diff --git a/datamodel/high/v3/package_test.go b/datamodel/high/v3/package_test.go index a9e9f4e..cc1e6e3 100644 --- a/datamodel/high/v3/package_test.go +++ b/datamodel/high/v3/package_test.go @@ -10,8 +10,8 @@ import ( "io/ioutil" ) -// Creating a new high-level OpenAPI 3+ document from an OpenAPI specification. -func Example() { +// An example of how to create a new high-level OpenAPI 3+ document from an OpenAPI specification. +func Example_createHighLevelOpenAPIDocument() { // Load in an OpenAPI 3+ specification as a byte slice. data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json") diff --git a/datamodel/low/v2/constants.go b/datamodel/low/v2/constants.go new file mode 100644 index 0000000..45e3737 --- /dev/null +++ b/datamodel/low/v2/constants.go @@ -0,0 +1,25 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package v2 + +const ( + DefinitionsLabel = "definitions" + SecurityDefinitionsLabel = "securityDefinitions" + ExamplesLabel = "examples" + HeadersLabel = "headers" + DefaultLabel = "default" + ItemsLabel = "items" + ParametersLabel = "parameters" + PathsLabel = "paths" + GetLabel = "get" + PostLabel = "post" + PatchLabel = "patch" + PutLabel = "put" + DeleteLabel = "delete" + OptionsLabel = "options" + HeadLabel = "head" + SecurityLabel = "security" + ScopesLabel = "scopes" + ResponsesLabel = "responses" +) diff --git a/datamodel/low/v2/definitions.go b/datamodel/low/v2/definitions.go index 975c081..2d251d7 100644 --- a/datamodel/low/v2/definitions.go +++ b/datamodel/low/v2/definitions.go @@ -10,43 +10,63 @@ import ( "gopkg.in/yaml.v3" ) -const ( - DefinitionsLabel = "definitions" - SecurityDefinitionsLabel = "securityDefinitions" -) - +// ParameterDefinitions is a low-level representation of a Swagger / OpenAPI 2 Parameters Definitions object. +// +// ParameterDefinitions holds parameters to be reused across operations. Parameter definitions can be +// referenced to the ones defined here. It does not define global operation parameters +// - https://swagger.io/specification/v2/#parametersDefinitionsObject type ParameterDefinitions struct { Definitions map[low.KeyReference[string]]low.ValueReference[*Parameter] } +// ResponsesDefinitions is a low-level representation of a Swagger / OpenAPI 2 Responses Definitions object. +// +// ResponsesDefinitions is an object to hold responses to be reused across operations. Response definitions can be +// referenced to the ones defined here. It does not define global operation responses +// - https://swagger.io/specification/v2/#responsesDefinitionsObject type ResponsesDefinitions struct { Definitions map[low.KeyReference[string]]low.ValueReference[*Response] } +// SecurityDefinitions is a low-level representation of a Swagger / OpenAPI 2 Security Definitions object. +// +// A declaration of the security schemes available to be used in the specification. This does not enforce the security +// schemes on the operations and only serves to provide the relevant details for each scheme +// - https://swagger.io/specification/v2/#securityDefinitionsObject type SecurityDefinitions struct { Definitions map[low.KeyReference[string]]low.ValueReference[*SecurityScheme] } +// Definitions is a low-level representation of a Swagger / OpenAPI 2 Definitions object +// +// An object to hold data types that can be consumed and produced by operations. These data types can be primitives, +// arrays or models. +// - https://swagger.io/specification/v2/#definitionsObject type Definitions struct { Schemas map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy] } +// FindSchema will attempt to locate a base.SchemaProxy instance using a name. func (d *Definitions) FindSchema(schema string) *low.ValueReference[*base.SchemaProxy] { return low.FindItemInMap[*base.SchemaProxy](schema, d.Schemas) } -func (pd *ParameterDefinitions) FindParameter(schema string) *low.ValueReference[*Parameter] { - return low.FindItemInMap[*Parameter](schema, pd.Definitions) +// FindParameter will attempt to locate a Parameter instance using a name. +func (pd *ParameterDefinitions) FindParameter(parameter string) *low.ValueReference[*Parameter] { + return low.FindItemInMap[*Parameter](parameter, pd.Definitions) } -func (r *ResponsesDefinitions) FindResponse(schema string) *low.ValueReference[*Response] { - return low.FindItemInMap[*Response](schema, r.Definitions) +// FindResponse will attempt to locate a Response instance using a name. +func (r *ResponsesDefinitions) FindResponse(response string) *low.ValueReference[*Response] { + return low.FindItemInMap[*Response](response, r.Definitions) } -func (s *SecurityDefinitions) FindSecurityDefinition(schema string) *low.ValueReference[*SecurityScheme] { - return low.FindItemInMap[*SecurityScheme](schema, s.Definitions) +// FindSecurityDefinition will attempt to locate a SecurityScheme using a name. +func (s *SecurityDefinitions) FindSecurityDefinition(securityDef string) *low.ValueReference[*SecurityScheme] { + return low.FindItemInMap[*SecurityScheme](securityDef, s.Definitions) } +// Build will extract all definitions into SchemaProxy instances. func (d *Definitions) Build(root *yaml.Node, idx *index.SpecIndex) error { errorChan := make(chan error) resultChan := make(chan definitionResult[*base.SchemaProxy]) @@ -58,12 +78,16 @@ func (d *Definitions) Build(root *yaml.Node, idx *index.SpecIndex) error { continue } totalDefinitions++ - var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*base.SchemaProxy], e chan error) { + var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, + r chan definitionResult[*base.SchemaProxy], e chan error) { + obj, err := low.ExtractObjectRaw[*base.SchemaProxy](value, idx) if err != nil { e <- err } - r <- definitionResult[*base.SchemaProxy]{k: label, v: low.ValueReference[*base.SchemaProxy]{Value: obj, ValueNode: value}} + r <- definitionResult[*base.SchemaProxy]{k: label, v: low.ValueReference[*base.SchemaProxy]{ + Value: obj, ValueNode: value, + }} } go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan) } @@ -86,6 +110,7 @@ func (d *Definitions) Build(root *yaml.Node, idx *index.SpecIndex) error { return nil } +// Build will extract all ParameterDefinitions into Parameter instances. func (pd *ParameterDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) error { errorChan := make(chan error) resultChan := make(chan definitionResult[*Parameter]) @@ -97,7 +122,9 @@ func (pd *ParameterDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) err continue } totalDefinitions++ - var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*Parameter], e chan error) { + var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, + r chan definitionResult[*Parameter], e chan error) { + obj, err := low.ExtractObjectRaw[*Parameter](value, idx) if err != nil { e <- err @@ -125,11 +152,13 @@ func (pd *ParameterDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) err return nil } +// re-usable struct for holding results as k/v pairs. type definitionResult[T any] struct { k *yaml.Node v low.ValueReference[T] } +// Build will extract all ResponsesDefinitions into Response instances. func (r *ResponsesDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) error { errorChan := make(chan error) resultChan := make(chan definitionResult[*Response]) @@ -141,7 +170,9 @@ func (r *ResponsesDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) erro continue } totalDefinitions++ - var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*Response], e chan error) { + var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, + r chan definitionResult[*Response], e chan error) { + obj, err := low.ExtractObjectRaw[*Response](value, idx) if err != nil { e <- err @@ -169,6 +200,7 @@ func (r *ResponsesDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) erro return nil } +// Build will extract all SecurityDefinitions into SecurityScheme instances. func (s *SecurityDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) error { errorChan := make(chan error) resultChan := make(chan definitionResult[*SecurityScheme]) @@ -180,12 +212,16 @@ func (s *SecurityDefinitions) Build(root *yaml.Node, idx *index.SpecIndex) error continue } totalDefinitions++ - var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, r chan definitionResult[*SecurityScheme], e chan error) { + var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, + r chan definitionResult[*SecurityScheme], e chan error) { + obj, err := low.ExtractObjectRaw[*SecurityScheme](value, idx) if err != nil { e <- err } - r <- definitionResult[*SecurityScheme]{k: label, v: low.ValueReference[*SecurityScheme]{Value: obj, ValueNode: value}} + r <- definitionResult[*SecurityScheme]{k: label, v: low.ValueReference[*SecurityScheme]{ + Value: obj, ValueNode: value, + }} } go buildFunc(defLabel, root.Content[i], idx, resultChan, errorChan) } diff --git a/datamodel/low/v2/examples.go b/datamodel/low/v2/examples.go index e791f20..ee90e30 100644 --- a/datamodel/low/v2/examples.go +++ b/datamodel/low/v2/examples.go @@ -9,18 +9,19 @@ import ( "gopkg.in/yaml.v3" ) -const ( - ExamplesLabel = "examples" -) - +// Examples represents a low-level Swagger / OpenAPI 2 Example object. +// Allows sharing examples for operation responses +// - https://swagger.io/specification/v2/#exampleObject type Examples struct { Values map[low.KeyReference[string]]low.ValueReference[any] } +// FindExample attempts to locate an example value, using a key label. func (e *Examples) FindExample(name string) *low.ValueReference[any] { return low.FindItemInMap[any](name, e.Values) } +// Build will extract all examples and will attempt to unmarshal content into a map or slice based on type. func (e *Examples) Build(root *yaml.Node, _ *index.SpecIndex) error { var keyNode, currNode *yaml.Node var err error diff --git a/datamodel/low/v2/header.go b/datamodel/low/v2/header.go index a0bab3a..d00b471 100644 --- a/datamodel/low/v2/header.go +++ b/datamodel/low/v2/header.go @@ -10,10 +10,10 @@ import ( "gopkg.in/yaml.v3" ) -const ( - HeadersLabel = "headers" -) - +// Header Represents a low-level Swagger / OpenAPI 2 Header object. +// +// A Header is essentially identical to a Parameter, except it does not contain 'name' or 'in' properties. +// - https://swagger.io/specification/v2/#headerObject type Header struct { Type low.NodeReference[string] Format low.NodeReference[string] @@ -36,10 +36,12 @@ type Header struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// FindExtension will attempt to locate an extension value using a name lookup. func (h *Header) FindExtension(ext string) *low.ValueReference[any] { return low.FindItemInMap[any](ext, h.Extensions) } +// Build will build out items, extensions and default value from the supplied node. func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error { h.Extensions = low.ExtractExtensions(root) items, err := low.ExtractObject[*Items](ItemsLabel, root, idx) diff --git a/datamodel/low/v2/items.go b/datamodel/low/v2/items.go index a87fd69..aed347c 100644 --- a/datamodel/low/v2/items.go +++ b/datamodel/low/v2/items.go @@ -10,11 +10,11 @@ import ( "gopkg.in/yaml.v3" ) -const ( - DefaultLabel = "default" - ItemsLabel = "items" -) - +// Items is a low-level representation of a Swagger / OpenAPI 2 Items object. +// +// Items is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are not +// located in "body" +// - https://swagger.io/specification/v2/#itemsObject type Items struct { Type low.NodeReference[string] Format low.NodeReference[string] @@ -35,6 +35,7 @@ type Items struct { MultipleOf low.NodeReference[int] } +// Build will build out items and default value. func (i *Items) Build(root *yaml.Node, idx *index.SpecIndex) error { items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx) if iErr != nil { diff --git a/datamodel/low/v2/operation.go b/datamodel/low/v2/operation.go index 5e57978..ce1a3c5 100644 --- a/datamodel/low/v2/operation.go +++ b/datamodel/low/v2/operation.go @@ -10,6 +10,10 @@ import ( "gopkg.in/yaml.v3" ) +// Operation represents a low-level Swagger / OpenAPI 2 Operation object. +// +// It describes a single API operation on a path. +// - https://swagger.io/specification/v2/#operationObject type Operation struct { Tags low.NodeReference[[]low.ValueReference[string]] Summary low.NodeReference[string] @@ -26,6 +30,7 @@ type Operation struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// Build will extract external docs, extensions, parameters, responses and security requirements. func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error { o.Extensions = low.ExtractExtensions(root) diff --git a/datamodel/low/v2/package_test.go b/datamodel/low/v2/package_test.go new file mode 100644 index 0000000..9ebb422 --- /dev/null +++ b/datamodel/low/v2/package_test.go @@ -0,0 +1,66 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package v2 + +import ( + "fmt" + "github.com/pb33f/libopenapi/datamodel" + "io/ioutil" +) + +// How to create a low-level Swagger / OpenAPI 2 Document from a specification +func Example_createLowLevelSwaggerDocument() { + + // How to create a low-level OpenAPI 2 Document + + // load petstore into bytes + petstoreBytes, _ := ioutil.ReadFile("../../../test_specs/petstorev2.json") + + // read in specification + info, _ := datamodel.ExtractSpecInfo(petstoreBytes) + + // build low-level document model + document, errors := CreateDocument(info) + + // if something went wrong, a slice of errors is returned + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %s\n", errors[i].Error()) + } + panic("cannot build document") + } + + // print out email address from the info > contact object. + fmt.Print(document.Info.Value.Contact.Value.Email.Value) + // Output: apiteam@swagger.io + +} + +// How to create a low-level Swagger / OpenAPI 2 Document from a specification +func ExampleCreateDocument() { + + // How to create a low-level OpenAPI 2 Document + + // load petstore into bytes + petstoreBytes, _ := ioutil.ReadFile("../../../test_specs/petstorev2.json") + + // read in specification + info, _ := datamodel.ExtractSpecInfo(petstoreBytes) + + // build low-level document model + document, errors := CreateDocument(info) + + // if something went wrong, a slice of errors is returned + if len(errors) > 0 { + for i := range errors { + fmt.Printf("error: %s\n", errors[i].Error()) + } + panic("cannot build document") + } + + // print out email address from the info > contact object. + fmt.Print(document.Info.Value.Contact.Value.Email.Value) + // Output: apiteam@swagger.io + +} diff --git a/datamodel/low/v2/parameter.go b/datamodel/low/v2/parameter.go index 4e55ec3..8dbb9a6 100644 --- a/datamodel/low/v2/parameter.go +++ b/datamodel/low/v2/parameter.go @@ -11,10 +11,37 @@ import ( "gopkg.in/yaml.v3" ) -const ( - ParametersLabel = "parameters" -) - +// Parameter represents a low-level Swagger / OpenAPI 2 Parameter object. +// +// A unique parameter is defined by a combination of a name and location. +// +// There are five possible parameter types. +// +// Path +// Used together with Path Templating, where the parameter value is actually part of the operation's URL. +// This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId. +// Query +// Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id. +// Header +// Custom headers that are expected as part of the request. +// Body +// The payload that's appended to the HTTP request. Since there can only be one payload, there can only be one body parameter. +// The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. +// Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation. +// Form +// Used to describe the payload of an HTTP request when either application/x-www-form-urlencoded, multipart/form-data +// or both are used as the content type of the request (in Swagger's definition, the consumes property of an operation). +// This is the only parameter type that can be used to send files, thus supporting the file type. Since form parameters +// are sent in the payload, they cannot be declared together with a body parameter for the same operation. Form +// parameters have a different format based on the content-type used (for further details, +// consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4): +// application/x-www-form-urlencoded - Similar to the format of Query parameters but as a payload. For example, +// foo=1&bar=swagger - both foo and bar are form parameters. This is normally used for simple parameters that are +// being transferred. +// multipart/form-data - each parameter takes a section in the payload with an internal header. For example, for +// the header Content-Disposition: form-data; name="submit-name" the name of the parameter is +// submit-name. This type of form parameters is more commonly used for file transfers +// https://swagger.io/specification/v2/#parameterObject type Parameter struct { Name low.NodeReference[string] In low.NodeReference[string] @@ -42,10 +69,12 @@ type Parameter struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// FindExtension attempts to locate a extension value given a name. func (p *Parameter) FindExtension(ext string) *low.ValueReference[any] { return low.FindItemInMap[any](ext, p.Extensions) } +// Build will extract out extensions, schema, items and default value func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error { p.Extensions = low.ExtractExtensions(root) sch, sErr := base.ExtractSchema(root, idx) diff --git a/datamodel/low/v2/path_item.go b/datamodel/low/v2/path_item.go index 951cb77..b43c22f 100644 --- a/datamodel/low/v2/path_item.go +++ b/datamodel/low/v2/path_item.go @@ -4,168 +4,177 @@ package v2 import ( - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "gopkg.in/yaml.v3" - "strings" - "sync" + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" + "gopkg.in/yaml.v3" + "strings" + "sync" ) +// PathItem represents a low-level Swagger / OpenAPI 2 PathItem object. +// +// 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 tooling, but will not know which operations and parameters +// are available. +// - https://swagger.io/specification/v2/#pathItemObject type PathItem struct { - Ref low.NodeReference[string] - Get low.NodeReference[*Operation] - Put low.NodeReference[*Operation] - Post low.NodeReference[*Operation] - Delete low.NodeReference[*Operation] - Options low.NodeReference[*Operation] - Head low.NodeReference[*Operation] - Patch low.NodeReference[*Operation] - Parameters low.NodeReference[[]low.ValueReference[*Parameter]] - Extensions map[low.KeyReference[string]]low.ValueReference[any] + Ref low.NodeReference[string] + Get low.NodeReference[*Operation] + Put low.NodeReference[*Operation] + Post low.NodeReference[*Operation] + Delete low.NodeReference[*Operation] + Options low.NodeReference[*Operation] + Head low.NodeReference[*Operation] + Patch low.NodeReference[*Operation] + Parameters low.NodeReference[[]low.ValueReference[*Parameter]] + Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// FindExtension will attempt to locate an extension given a name. func (p *PathItem) FindExtension(ext string) *low.ValueReference[any] { - return low.FindItemInMap[any](ext, p.Extensions) + return low.FindItemInMap[any](ext, p.Extensions) } +// Build will extract extensions, parameters and operations for all methods. Every method is handled +// asynchronously, in order to keep things moving quickly for complex operations. func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error { - p.Extensions = low.ExtractExtensions(root) - skip := false - var currentNode *yaml.Node + p.Extensions = low.ExtractExtensions(root) + skip := false + var currentNode *yaml.Node - var wg sync.WaitGroup - var errors []error + var wg sync.WaitGroup + var errors []error - var ops []low.NodeReference[*Operation] + var ops []low.NodeReference[*Operation] - // extract parameters - params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx) - if pErr != nil { - return pErr - } - if params != nil { - p.Parameters = low.NodeReference[[]low.ValueReference[*Parameter]]{ - Value: params, - KeyNode: ln, - ValueNode: vn, - } - } + // extract parameters + params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx) + if pErr != nil { + return pErr + } + if params != nil { + p.Parameters = low.NodeReference[[]low.ValueReference[*Parameter]]{ + Value: params, + KeyNode: ln, + ValueNode: vn, + } + } - for i, pathNode := range root.Content { - if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") { - skip = true - continue - } - // because (for some reason) the spec for swagger docs allows for a '$ref' property for path items. - // this is kinda nuts, because '$ref' is a reserved keyword for JSON references, which is ALSO used - // in swagger. Why this choice was made, I do not know. - if strings.Contains(strings.ToLower(pathNode.Value), "$ref") { - rn := root.Content[i+1] - p.Ref = low.NodeReference[string]{ - Value: rn.Value, - ValueNode: rn, - KeyNode: pathNode, - } - skip = true - continue - } - if skip { - skip = false - continue - } - if i%2 == 0 { - currentNode = pathNode - continue - } + for i, pathNode := range root.Content { + if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") { + skip = true + continue + } + // because (for some reason) the spec for swagger docs allows for a '$ref' property for path items. + // this is kinda nuts, because '$ref' is a reserved keyword for JSON references, which is ALSO used + // in swagger. Why this choice was made, I do not know. + if strings.Contains(strings.ToLower(pathNode.Value), "$ref") { + rn := root.Content[i+1] + p.Ref = low.NodeReference[string]{ + Value: rn.Value, + ValueNode: rn, + KeyNode: pathNode, + } + skip = true + continue + } + if skip { + skip = false + continue + } + if i%2 == 0 { + currentNode = pathNode + continue + } - // the only thing we now care about is handling operations, filter out anything that's not a verb. - switch currentNode.Value { - case GetLabel: - break - case PostLabel: - break - case PutLabel: - break - case PatchLabel: - break - case DeleteLabel: - break - case HeadLabel: - break - case OptionsLabel: - break - default: - continue // ignore everything else. - } + // the only thing we now care about is handling operations, filter out anything that's not a verb. + switch currentNode.Value { + case GetLabel: + break + case PostLabel: + break + case PutLabel: + break + case PatchLabel: + break + case DeleteLabel: + break + case HeadLabel: + break + case OptionsLabel: + break + default: + continue // ignore everything else. + } - var op Operation + var op Operation - wg.Add(1) + wg.Add(1) - go low.BuildModelAsync(pathNode, &op, &wg, &errors) + go low.BuildModelAsync(pathNode, &op, &wg, &errors) - opRef := low.NodeReference[*Operation]{ - Value: &op, - KeyNode: currentNode, - ValueNode: pathNode, - } + opRef := low.NodeReference[*Operation]{ + Value: &op, + KeyNode: currentNode, + ValueNode: pathNode, + } - ops = append(ops, opRef) + ops = append(ops, opRef) - switch currentNode.Value { - case GetLabel: - p.Get = opRef - case PostLabel: - p.Post = opRef - case PutLabel: - p.Put = opRef - case PatchLabel: - p.Patch = opRef - case DeleteLabel: - p.Delete = opRef - case HeadLabel: - p.Head = opRef - case OptionsLabel: - p.Options = opRef - } - } + switch currentNode.Value { + case GetLabel: + p.Get = opRef + case PostLabel: + p.Post = opRef + case PutLabel: + p.Put = opRef + case PatchLabel: + p.Patch = opRef + case DeleteLabel: + p.Delete = opRef + case HeadLabel: + p.Head = opRef + case OptionsLabel: + p.Options = opRef + } + } - //all operations have been superficially built, - //now we need to build out the operation, we will do this asynchronously for speed. - opBuildChan := make(chan bool) - opErrorChan := make(chan error) + //all operations have been superficially built, + //now we need to build out the operation, we will do this asynchronously for speed. + opBuildChan := make(chan bool) + opErrorChan := make(chan error) - var buildOpFunc = func(op low.NodeReference[*Operation], ch chan<- bool, errCh chan<- error) { - er := op.Value.Build(op.ValueNode, idx) - if er != nil { - errCh <- er - } - ch <- true - } + var buildOpFunc = func(op low.NodeReference[*Operation], ch chan<- bool, errCh chan<- error) { + er := op.Value.Build(op.ValueNode, idx) + if er != nil { + errCh <- er + } + ch <- true + } - if len(ops) <= 0 { - return nil // nothing to do. - } + if len(ops) <= 0 { + return nil // nothing to do. + } - for _, op := range ops { - go buildOpFunc(op, opBuildChan, opErrorChan) - } + for _, op := range ops { + go buildOpFunc(op, opBuildChan, opErrorChan) + } - n := 0 - total := len(ops) - for n < total { - select { - case buildError := <-opErrorChan: - return buildError - case <-opBuildChan: - n++ - } - } + n := 0 + total := len(ops) + for n < total { + select { + case buildError := <-opErrorChan: + return buildError + case <-opBuildChan: + n++ + } + } - // make sure we don't exit before the path is finished building. - if len(ops) > 0 { - wg.Wait() - } + // make sure we don't exit before the path is finished building. + if len(ops) > 0 { + wg.Wait() + } - return nil + return nil } diff --git a/datamodel/low/v2/paths.go b/datamodel/low/v2/paths.go index 7e98387..e871bf1 100644 --- a/datamodel/low/v2/paths.go +++ b/datamodel/low/v2/paths.go @@ -10,22 +10,13 @@ import ( "strings" ) -const ( - PathsLabel = "paths" - GetLabel = "get" - PostLabel = "post" - PatchLabel = "patch" - PutLabel = "put" - DeleteLabel = "delete" - OptionsLabel = "options" - HeadLabel = "head" -) - +// Paths represents a low-level Swagger / OpenAPI Paths object. type Paths struct { PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem] Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// FindPath attempts to locate a PathItem instance, given a path key. func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] { for k, j := range p.PathItems { if k.Value == path { @@ -35,10 +26,12 @@ func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] { return nil } +// FindExtension will attempt to locate an extension value given a name. func (p *Paths) FindExtension(ext string) *low.ValueReference[any] { return low.FindItemInMap[any](ext, p.Extensions) } +// Build will extract extensions and paths from node. func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error { p.Extensions = low.ExtractExtensions(root) skip := false diff --git a/datamodel/low/v2/response.go b/datamodel/low/v2/response.go index ad42df5..6de361a 100644 --- a/datamodel/low/v2/response.go +++ b/datamodel/low/v2/response.go @@ -10,10 +10,10 @@ import ( "gopkg.in/yaml.v3" ) -const ( - ResponsesLabel = "responses" -) - +// Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one. +// +// Response describes a single response from an API Operation +// - https://swagger.io/specification/v2/#responseObject type Response struct { Description low.NodeReference[string] Schema low.NodeReference[*base.SchemaProxy] @@ -22,14 +22,17 @@ type Response struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// FindExtension will attempt to locate an extension value given a key to lookup. func (r *Response) FindExtension(ext string) *low.ValueReference[any] { return low.FindItemInMap[any](ext, r.Extensions) } +// FindHeader will attempt to locate a Header value, given a key func (r *Response) FindHeader(hType string) *low.ValueReference[*Header] { return low.FindItemInMap[*Header](hType, r.Headers.Value) } +// Build will extract schema, extensions, examples and headers from node func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error { r.Extensions = low.ExtractExtensions(root) s, err := base.ExtractSchema(root, idx) diff --git a/datamodel/low/v2/responses.go b/datamodel/low/v2/responses.go index de99f86..5b61a4f 100644 --- a/datamodel/low/v2/responses.go +++ b/datamodel/low/v2/responses.go @@ -11,12 +11,14 @@ import ( "gopkg.in/yaml.v3" ) +// Responses is a low-level representation of a Swagger / OpenAPI 2 Responses object. type Responses struct { Codes map[low.KeyReference[string]]low.ValueReference[*Response] Default low.NodeReference[*Response] Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// Build will extract default value and extensions from node. func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error { r.Extensions = low.ExtractExtensions(root) @@ -42,6 +44,7 @@ func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error { return nil } +// FindResponseByCode will attempt to locate a Response instance using an HTTP response code string. func (r *Responses) FindResponseByCode(code string) *low.ValueReference[*Response] { return low.FindItemInMap[*Response](code, r.Codes) } diff --git a/datamodel/low/v2/scopes.go b/datamodel/low/v2/scopes.go index 4eaa0a4..b320f6a 100644 --- a/datamodel/low/v2/scopes.go +++ b/datamodel/low/v2/scopes.go @@ -10,15 +10,21 @@ import ( "gopkg.in/yaml.v3" ) +// Scopes is a low-level representation of a Swagger / OpenAPI 2 OAuth2 Scopes object. +// +// Scopes lists the available scopes for an OAuth2 security scheme. +// - https://swagger.io/specification/v2/#scopesObject type Scopes struct { Values map[low.KeyReference[string]]low.ValueReference[string] Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// FindScope will attempt to locate a scope string using a key. func (s *Scopes) FindScope(scope string) *low.ValueReference[string] { return low.FindItemInMap[string](scope, s.Values) } +// Build will extract scope values and extensions from node. func (s *Scopes) Build(root *yaml.Node, idx *index.SpecIndex) error { s.Extensions = low.ExtractExtensions(root) valueMap := make(map[low.KeyReference[string]]low.ValueReference[string]) diff --git a/datamodel/low/v2/security_requirement.go b/datamodel/low/v2/security_requirement.go index fb337a8..bdd97c5 100644 --- a/datamodel/low/v2/security_requirement.go +++ b/datamodel/low/v2/security_requirement.go @@ -9,14 +9,18 @@ import ( "gopkg.in/yaml.v3" ) -const ( - SecurityLabel = "security" -) - +// SecurityRequirement is a low-level representation of a Swagger / OpenAPI 2 SecurityRequirement object. +// +// SecurityRequirement lists the required security schemes to execute this operation. The object can have multiple +// security schemes declared in it which are all required (that is, there is a logical AND between the schemes). +// +// The name used for each property MUST correspond to a security scheme declared in the Security Definitions +// - https://swagger.io/specification/v2/#securityDefinitionsObject type SecurityRequirement struct { Values low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]] } +// Build will extract security requirements from the node (the structure is odd, to be honest) func (s *SecurityRequirement) Build(root *yaml.Node, _ *index.SpecIndex) error { var labelNode *yaml.Node var arr []low.ValueReference[string] diff --git a/datamodel/low/v2/security_scheme.go b/datamodel/low/v2/security_scheme.go index b5fbe5f..8360b96 100644 --- a/datamodel/low/v2/security_scheme.go +++ b/datamodel/low/v2/security_scheme.go @@ -9,10 +9,12 @@ import ( "gopkg.in/yaml.v3" ) -const ( - ScopesLabel = "scopes" -) - +// SecurityScheme is a low-level representation of a Swagger / OpenAPI 2 SecurityScheme object. +// +// SecurityScheme allows the definition of a security scheme that can be used by the operations. Supported schemes are +// basic authentication, an API key (either as a header or as a query parameter) and OAuth2's common flows +// (implicit, password, application and access code) +// - https://swagger.io/specification/v2/#securityDefinitionsObject type SecurityScheme struct { Type low.NodeReference[string] Description low.NodeReference[string] @@ -25,6 +27,7 @@ type SecurityScheme struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } +// Build will extract extensions and scopes from the node. func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error { ss.Extensions = low.ExtractExtensions(root) diff --git a/datamodel/low/v2/swagger.go b/datamodel/low/v2/swagger.go index 49ad139..5e07e71 100644 --- a/datamodel/low/v2/swagger.go +++ b/datamodel/low/v2/swagger.go @@ -1,6 +1,14 @@ // Copyright 2022 Princess B33f Heavy Industries / Dave Shanley // SPDX-License-Identifier: MIT +// Package v2 represents all Swagger / OpenAPI 2 low-level models. +// +// Low-level models are more difficult to navigate than higher-level models, however they are packed with all the +// raw AST and node data required to perform any kind of analysis on the underlying data. +// +// Every property is wrapped in a NodeReference or a KeyReference or a ValueReference. +// +// IMPORTANT: As a general rule, Swagger / OpenAPI 2 should be avoided for new projects. package v2 import ( @@ -12,29 +20,98 @@ import ( "gopkg.in/yaml.v3" ) +// processes a property of a Swagger document asynchronously using bool and error channels for signals. type documentFunction func(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) +// Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification. type Swagger struct { - Swagger low.ValueReference[string] - Info low.NodeReference[*base.Info] - Host low.NodeReference[string] - BasePath low.NodeReference[string] - Schemes low.NodeReference[[]low.ValueReference[string]] - Consumes low.NodeReference[[]low.ValueReference[string]] - Produces low.NodeReference[[]low.ValueReference[string]] - Paths low.NodeReference[*Paths] - Definitions low.NodeReference[*Definitions] + + // Swagger is the version of Swagger / OpenAPI being used, extracted from the 'swagger: 2.x' definition. + Swagger low.ValueReference[string] + + // Info represents a specification Info definition. + // Provides metadata about the API. The metadata can be used by the clients if needed. + // - https://swagger.io/specification/v2/#infoObject + Info low.NodeReference[*base.Info] + + // Host is The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor + // sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used + // (including the port). The host does not support path templating. + Host low.NodeReference[string] + + // BasePath is The base path on which the API is served, which is relative to the host. If it is not included, + // the API is served directly under the host. The value MUST start with a leading slash (/). + // The basePath does not support path templating. + BasePath low.NodeReference[string] + + // Schemes represents the transfer protocol of the API. Values MUST be from the list: "http", "https", "ws", "wss". + // If the schemes is not included, the default scheme to be used is the one used to access + // the Swagger definition itself. + Schemes low.NodeReference[[]low.ValueReference[string]] + + // Consumes is a list of MIME types the APIs can consume. This is global to all APIs but can be overridden on + // specific API calls. Value MUST be as described under Mime Types. + Consumes low.NodeReference[[]low.ValueReference[string]] + + // Produces is a list of MIME types the APIs can produce. This is global to all APIs but can be overridden on + // specific API calls. Value MUST be as described under Mime Types. + Produces low.NodeReference[[]low.ValueReference[string]] + + // Paths are the paths and operations for the API. Perhaps the most important part of the specification. + // - https://swagger.io/specification/v2/#pathsObject + Paths low.NodeReference[*Paths] + + // Definitions is an object to hold data types produced and consumed by operations. It's composed of Schema instances + // - https://swagger.io/specification/v2/#definitionsObject + Definitions low.NodeReference[*Definitions] + + // SecurityDefinitions represents security scheme definitions that can be used across the specification. + // - https://swagger.io/specification/v2/#securityDefinitionsObject SecurityDefinitions low.NodeReference[*SecurityDefinitions] - Parameters low.NodeReference[*ParameterDefinitions] - Responses low.NodeReference[*ResponsesDefinitions] - Security low.NodeReference[[]low.ValueReference[*SecurityRequirement]] - Tags low.NodeReference[[]low.ValueReference[*base.Tag]] - ExternalDocs low.NodeReference[*base.ExternalDoc] - Extensions map[low.KeyReference[string]]low.ValueReference[any] - Index *index.SpecIndex - SpecInfo *datamodel.SpecInfo + + // Parameters is an object to hold parameters that can be used across operations. + // This property does not define global parameters for all operations. + // - https://swagger.io/specification/v2/#parametersDefinitionsObject + Parameters low.NodeReference[*ParameterDefinitions] + + // Responses is an object to hold responses that can be used across operations. + // This property does not define global responses for all operations. + // - https://swagger.io/specification/v2/#responsesDefinitionsObject + Responses low.NodeReference[*ResponsesDefinitions] + + // Security is a declaration of which security schemes are applied for the API as a whole. The list of values + // describes alternative security schemes that can be used (that is, there is a logical OR between the security + // requirements). Individual operations can override this definition. + // - https://swagger.io/specification/v2/#securityRequirementObject + Security low.NodeReference[[]low.ValueReference[*SecurityRequirement]] + + // Tags are A list of tags used by the specification with additional metadata. + // The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used + // by the Operation Object must be declared. The tags that are not declared may be organized randomly or based + // on the tools' logic. Each tag name in the list MUST be unique. + // - https://swagger.io/specification/v2/#tagObject + Tags low.NodeReference[[]low.ValueReference[*base.Tag]] + + // ExternalDocs is an instance of base.ExternalDoc for.. well, obvious really, innit mate? + ExternalDocs low.NodeReference[*base.ExternalDoc] + + // Extensions contains all custom extensions defined for the top-level document. + Extensions map[low.KeyReference[string]]low.ValueReference[any] + + // Index is a reference to the index.SpecIndex that was created for the document and used + // as a guide when building out the Document. Ideal if further processing is required on the model and + // the original details are required to continue the work. + // + // This property is not a part of the OpenAPI schema, this is custom to libopenapi. + Index *index.SpecIndex + + // SpecInfo is a reference to the datamodel.SpecInfo instance created when the specification was read. + // + // This property is not a part of the OpenAPI schema, this is custom to libopenapi. + SpecInfo *datamodel.SpecInfo } +// FindExte func (s *Swagger) FindExtension(ext string) *low.ValueReference[any] { return low.FindItemInMap[any](ext, s.Extensions) } diff --git a/datamodel/low/v3/document.go b/datamodel/low/v3/document.go index d155522..f906e0a 100644 --- a/datamodel/low/v3/document.go +++ b/datamodel/low/v3/document.go @@ -1,7 +1,7 @@ // Copyright 2022 Princess B33f Heavy Industries / Dave Shanley // SPDX-License-Identifier: MIT -// Package v3 represents all OpenAPI 3+ low-level models. High-level models are more difficult to navigate +// Package v3 represents all OpenAPI 3+ low-level models. Low-level models are more difficult to navigate // than higher-level models, however they are packed with all the raw AST and node data required to perform // any kind of analysis on the underlying data. // diff --git a/datamodel/low/v3/examples_test.go b/datamodel/low/v3/examples_test.go index 6e7017f..8f71efa 100644 --- a/datamodel/low/v3/examples_test.go +++ b/datamodel/low/v3/examples_test.go @@ -9,7 +9,8 @@ import ( "io/ioutil" ) -func Example_createLowDocument() { +// How to create a low-level OpenAPI 3+ Document from an OpenAPI specification +func Example_createLowLevelOpenAPIDocument() { // How to create a low-level OpenAPI 3 Document // load petstore into bytes