402 lines
9.7 KiB
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"`
|
|
}
|