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"` }