opier/openapi/openapi.go

402 lines
9.7 KiB
Go

package openapi // import "udico.de/uditaren/opier/openapi"
import (
"fmt"
"gopkg.in/yaml.v3"
"os"
"strings"
"unicode"
)
type OpenAPI struct {
Version string `yaml:"openapi"`
Info struct {
Title string
Version string
Description string
}
Tags []*TagObject `yaml:"tags"`
Paths map[string]*PathItem `yaml:"paths"`
Components *Components
Servers []*Server `yaml:"servers"`
}
func Load(fname string) (*OpenAPI, error) {
f, err := os.Open(fname)
if err != nil {
return nil, err
}
tRet := &OpenAPI{}
tY := yaml.NewDecoder(f)
err = tY.Decode(tRet)
return tRet, err
}
func (o OpenAPI) ResolveParameter(name string) *Parameter {
if !strings.HasPrefix(name, "#/components/parameters/") {
return nil
}
tName := strings.TrimPrefix(name, "#/components/parameters/")
for pName, param := range o.Components.Parameters {
if tName == pName {
tParam := param
return &tParam
}
}
return nil
}
func (o OpenAPI) ResolveSchema(name string) *Schema {
if !strings.HasPrefix(name, "#/components/schemas/") {
return nil
}
tName := strings.TrimPrefix(name, "#/components/schemas/")
for pSch, tSchema := range o.Components.Schemas {
if tName == pSch {
tSchemaP := tSchema
tSchemaP.name = tName
return &tSchemaP
}
}
return nil
}
type Server struct {
Url string `yaml:"url"`
Description string `yaml:"description"`
}
type TagObject struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
}
// PathItem Describes the operations available on a single path. A Path Item MAY be empty, due to ACL constraints.
// The path itself is still exposed to the documentation viewer but they will not know which operations and parameters
// are available.
type PathItem struct {
// Allows for an external definition of this path item. The referenced structure MUST be in the format of a
// PathItem Object. In case a PathItem Object field appears both in the defined object and the referenced object,
// the behavior is undefined.
Ref string `yaml:"$ref"`
Parameters []*Parameter
// An optional, string summary, intended to apply to all operations in this path.
Summary string
// An optional, string description, intended to apply to all operations in this path. CommonMark syntax MAY be
// used for rich text representation.
Description string
Get *Operation
Put *Operation
Post *Operation
Delete *Operation
Options *Operation
Head *Operation
Patch *Operation
Trace *Operation
// for internal bookkeeping
Name string
}
// Never feed the mighty spaghetti (code) monster :(
func (p *PathItem) Operations() map[string]*Operation {
tRet := make(map[string]*Operation)
if p.Get != nil {
p.Get.path = p
p.Get.method = "get"
tRet["get"] = p.Get
}
if p.Put != nil {
p.Put.path = p
p.Put.method = "put"
tRet["put"] = p.Put
}
if p.Post != nil {
p.Post.path = p
p.Post.method = "post"
tRet["post"] = p.Post
}
if p.Delete != nil {
p.Delete.path = p
p.Delete.method = "delete"
tRet["delete"] = p.Delete
}
if p.Options != nil {
p.Options.path = p
p.Options.method = "options"
tRet["options"] = p.Options
}
if p.Head != nil {
p.Head.path = p
p.Head.method = "head"
tRet["head"] = p.Head
}
if p.Patch != nil {
p.Patch.path = p
p.Patch.method = "patch"
tRet["patch"] = p.Patch
}
if p.Trace != nil {
p.Trace.path = p
p.Trace.method = "trace"
tRet["trace"] = p.Trace
}
return tRet
}
// Operation describes a single API operation on a path.
type Operation struct {
// A list of tags for API documentation control. Tags can be used for logical grouping of operations
// by resources or any other qualifier.
Tags []string
// A short summary of what the operation does.
Summary string
// A verbose explanation of the operation behavior. CommonMark syntax MAY be used for rich text representation.
Description string
// Additional external documentation for this operation.
//externalDocs
// Unique string used to identify the operation. The id MUST be unique among all operations described in the API.
// The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an
// operation, therefore, it is RECOMMENDED to follow common programming naming conventions.
OperationId string `yaml:"operationId"`
//Parameters [] ...
Parameters []*Parameter
RequestBody *RequestBodyObject `yaml:"requestBody"`
// callbacks
// Responses Respons
Responses map[string]*ResponseObject
Deprecated bool
// A declaration of which security mechanisms can be used for this operation.
// The list of values includes alternative security requirement objects that can be used. Only one of the security
// requirement objects need to be satisfied to authorize a request. To make security optional, an empty security
// requirement ({}) can be included in the array. This definition overrides any declared top-level security.
// To remove a top-level security declaration, an empty array can be used.
Security []map[string][]string `yaml:"security"`
// An alternative server array to service this operation. If an alternative server object is specified at the Path
// Item Object or Root level, it will be overridden by this value.
// Servers
// we need a backref to the Path for building nice maps
path *PathItem
method string
}
func (o Operation) Path() *PathItem {
return o.path
}
func (o Operation) Method() string {
return strings.ToUpper(o.method)
}
type RequestBodyObject struct {
Description string `yaml:"description"`
Content map[string]*MediaTypeObject `yaml:"content"`
Required bool `yaml:"required"`
}
type ResponseObject struct {
Ref string `yaml:"$ref"`
Description string `yaml:"description"`
Headers map[string]*Parameter `yaml:"headers"`
Content map[string]*MediaTypeObject `yaml:"content"`
Links map[string]*LinkObject `yaml:"links"`
}
func (r ResponseObject) String() string {
ret := ""
for _, v := range r.Content {
if v.Schema != nil {
if v.Schema.Ref != "" {
ret = v.Schema.Ref
} else {
ret = "IMPLEMENT ME!!!"
}
break // the for loop
}
}
return ret
}
// The Header Object follows the structure of the Parameter Object with the following changes:
// * name MUST NOT be specified, it is given in the corresponding headers map.
// * in MUST NOT be specified, it is implicitly in header.
// * All traits that are affected by the location MUST be applicable to a location of header (for example, style).
type HeaderObject struct {
Parameter
}
type LinkObject struct {
// nope, we just don't want to have these …
}
type Components struct {
//SecuritySchemas
//Responses map[string, ResponseObject]
Parameters map[string]Parameter
Schemas map[string]Schema
}
type ParameterLocation string
const (
QUERY ParameterLocation = "query"
HEAD = "header"
PATH = "path"
COOKIE = "cookie"
)
type Parameter struct {
// only if this a reference object instead of a parameter obect
Ref string `yaml:"$ref"`
Name string
In ParameterLocation
Description string
Required bool
Deprecated bool
AllowEmptyValue bool `yaml:"allowEmptyValue"`
///--- either
Style string
Explode bool
AllowReserved bool `yaml:"allowReserved"`
Schema *Schema
// example
// examples
///--- or
Content map[string]*MediaTypeObject
}
type Schema struct {
// may be a schema reference:
Ref string `yaml:"$ref"`
Type string
// when type==array:
Items *Schema
// when type==object
Properties map[string]*Schema
Format string
Minimum int
Maximum int
Default interface{}
Enum []interface{}
XEnumVarnames []string `yaml:"x-enum-varnames"`
Required []string
Description string
// Only filled in by ResolveSchema(...)
name string
}
func (s Schema) Name() string {
return s.name
}
func (s Schema) TypeOrReference() string {
if s.Ref != "" {
return s.Ref
}
return s.Type
}
func (s Schema) GoType() string {
tRet := s.Type
if s.Ref != "" {
idx := strings.LastIndex(s.Ref, "/")
tRet = "*" + s.Ref[idx+1:]
} else {
switch s.Type {
case "integer":
tRet = "int"
case "boolean":
tRet = "bool"
case "array":
tRet = fmt.Sprintf("[]%v", s.Items.GoType())
case "object":
// should not happen, but in order to not to break things
// return an empty struct
tRet = "struct{/*object*/}"
case "": // arrays may not be tagged as such. deduce from the items element:
if s.Items != nil {
tRet = fmt.Sprintf("[]%v", s.Items.GoType())
}
}
}
return tRet
}
// EnumNames returns the String representations for this enums values and the length of the longest name as well.
func (s Schema) EnumNames() ([]string, int) {
tLen := 0
tRet := make([]string, len(s.Enum), len(s.Enum))
for k, v := range s.Enum {
tName := NormalizeName(fmt.Sprintf("%v", v))
if len(s.XEnumVarnames) == len(s.Enum) {
// read the desired enum name instead
tName = s.XEnumVarnames[k]
}
tRet[k] = tName
if len(tName) > tLen {
tLen = len(tName)
}
}
return tRet, tLen
}
type Referencable interface {
}
func NormalizeName(aName string) string {
tRet := &strings.Builder{}
capitalizeNext := true
for _, rune := range aName {
if capitalizeNext {
tRet.WriteRune(unicode.ToUpper(rune))
capitalizeNext = false
} else {
if rune == '-' {
capitalizeNext = true
} else {
tRet.WriteRune(rune)
}
}
}
return tRet.String()
}
type MediaTypeObject struct {
// The schema defining the content of the request, response, or parameter.
Schema *Schema `yaml:"schema"`
Example string `yaml:"example"`
//examples
//encoding
}
type SchemaObject struct {
Ref string `yaml:"$ref"`
}