From 649a93b0082db6def7c42bf88a78bfa2e392e99e Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Sat, 17 Sep 2022 09:57:29 -0400 Subject: [PATCH] More v2 high-level docs started including content from the schema to make it easier to understand when using the code as a complete guide, without pivoting to the spec. --- README.md | 14 ++++++++------ datamodel/high/base/info.go | 4 ++++ datamodel/high/v3/callback.go | 5 +++++ datamodel/high/v3/components.go | 13 +++++++++++++ datamodel/high/v3/encoding.go | 2 +- datamodel/high/v3/link.go | 10 ++++++++++ datamodel/high/v3/media_type.go | 6 ++++++ datamodel/high/v3/oauth_flow.go | 4 ++++ datamodel/high/v3/oauth_flows.go | 4 ++++ datamodel/high/v3/operation.go | 6 ++++++ datamodel/high/v3/parameter.go | 6 ++++++ datamodel/high/v3/path_item.go | 8 ++++++++ datamodel/high/v3/paths.go | 8 ++++++++ datamodel/high/v3/request_body.go | 1 + datamodel/high/v3/response.go | 7 +++++++ datamodel/high/v3/responses.go | 18 ++++++++++++++++++ datamodel/high/v3/security_requirement.go | 14 ++++++++++++++ 17 files changed, 123 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a71b556..0d9d69a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ There is already a great OpenAPI library for golang, it's called [kin-openapi](h [kin-openapi](https://github.com/getkin/kin-openapi) is great, and you should use it. -**However, it's missing one critical feature** +> **_However, kin-openapi missing one critical feature_**... It's so important, this library exists because of it. When building tooling that needs to analyze OpenAPI specifications at a *low* level, [kin-openapi](https://github.com/getkin/kin-openapi) **runs out of power** when you need to know the original line numbers and columns, or comments within all keys and values in the spec. @@ -23,6 +23,8 @@ All that data is **lost** when the spec is loaded in by [kin-openapi](https://gi because the library will unmarshal data directly into structs, which works great - if you don't need access to the original specification low level details. +Want to build a linter? Analysis tool? Renderer that retains original positions? + ## libopenapi retains _everything_. libopenapi has been designed to retain all of that really low-level detail about the AST, line numbers, column numbers, comments, @@ -32,11 +34,11 @@ libopenapi has a **porcelain** (high-level) and a **plumbing** (low-level) API. ability to `GoLow` and dive from the high-level model, down to the low-level model and look-up any detail about the underlying raw data backing that model. -This library exists because this very need existed inside [VMware](https://vmware.com), we built our own internal -version of libopenapi, which isn't something that can be released as it's bespoke. +This library exists because this very need existed inside [VMware](https://vmware.com). The company built an internal +version of libopenapi, which isn't something that can be released as it's customized for VMware (and it's incomplete). -libopenapi is the result of years of learning and battle testing OpenAPI in golang. This library represents what we -would have created, if we knew then - what we know now. +libopenapi is the result of years of learning and battle testing OpenAPI in golang. This library represents what would +have been created, if we knew then - what we know now. > If you need to know which line, or column a key or value for something is? **libopenapi has you covered** @@ -153,7 +155,7 @@ document, _ := NewDocument(petstore) v3Model, _ := document.BuildV3Model() // extract the RequestBody from the 'put' operation under the /pet path -reqBody := h.Paths.PathItems["/pet"].Put.RequestBody +reqBody := document.Paths.PathItems["/pet"].Put.RequestBody // dropdown to the low-level API for RequestBody lowReqBody := reqBody.GoLow() diff --git a/datamodel/high/base/info.go b/datamodel/high/base/info.go index 3e5025c..294522b 100644 --- a/datamodel/high/base/info.go +++ b/datamodel/high/base/info.go @@ -8,6 +8,10 @@ import ( ) // Info represents an Info object as defined by both OpenAPI 2 and OpenAPI 3. +// +// The object provides metadata about the API. The metadata MAY be used by the clients if needed, and MAY be presented +// in editing or documentation generation tools for convenience. +// // v2 - https://swagger.io/specification/v2/#infoObject // v3 - https://spec.openapis.org/oas/v3.1.0#info-object type Info struct { diff --git a/datamodel/high/v3/callback.go b/datamodel/high/v3/callback.go index f534b4c..3c60612 100644 --- a/datamodel/high/v3/callback.go +++ b/datamodel/high/v3/callback.go @@ -6,6 +6,11 @@ package v3 import low "github.com/pb33f/libopenapi/datamodel/low/v3" // Callback represents a high-level Callback object for OpenAPI 3+. +// +// A map of possible out-of band callbacks related to the parent operation. Each value in the map is a +// PathItem Object that describes a set of requests that may be initiated by the API provider and the expected +// responses. The key value used to identify the path item object is an expression, evaluated at runtime, +// that identifies a URL to use for the callback operation. // - https://spec.openapis.org/oas/v3.1.0#callback-object type Callback struct { Expression map[string]*PathItem diff --git a/datamodel/high/v3/components.go b/datamodel/high/v3/components.go index ddc069d..fff9fb4 100644 --- a/datamodel/high/v3/components.go +++ b/datamodel/high/v3/components.go @@ -11,6 +11,7 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// used for internal channel co-ordination for building out different component types. const ( responses = iota parameters @@ -22,6 +23,11 @@ const ( callbacks ) +// Components represents a high-level OpenAPI 3+ Components Object, that is backed by a low-level one. +// +// Holds a set of reusable objects for different aspects of the OAS. All objects defined within the components object +// will have no effect on the API unless they are explicitly referenced from properties outside the components object. +// - https://spec.openapis.org/oas/v3.1.0#components-object type Components struct { Schemas map[string]*highbase.SchemaProxy Responses map[string]*Response @@ -36,6 +42,9 @@ type Components struct { low *low.Components } +// NewComponents will create new high-level instance of Components from a low-level one. Components can be considerable +// in scope, with a lot of different properties across different categories. All components are built asynchronously +// in order to keep things fast. func NewComponents(comp *low.Components) *Components { c := new(Components) c.low = comp @@ -140,16 +149,19 @@ func NewComponents(comp *low.Components) *Components { return c } +// contains a component build result. type componentResult[T any] struct { res T key string comp int } +// build out a component. func buildComponent[N any, O any](comp int, key string, orig O, c chan componentResult[N], f func(O) N) { c <- componentResult[N]{comp: comp, res: f(orig), key: key} } +// build out a schema func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*base.SchemaProxy], c chan componentResult[*highbase.SchemaProxy]) { var sch *highbase.SchemaProxy sch = highbase.NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{ @@ -159,6 +171,7 @@ func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference c <- componentResult[*highbase.SchemaProxy]{res: sch, key: key.Value} } +// GoLow returns the low-level Components instance used to create the high-level one. func (c *Components) GoLow() *low.Components { return c.low } diff --git a/datamodel/high/v3/encoding.go b/datamodel/high/v3/encoding.go index 6d8b92b..0399fce 100644 --- a/datamodel/high/v3/encoding.go +++ b/datamodel/high/v3/encoding.go @@ -36,7 +36,7 @@ func (e *Encoding) GoLow() *low.Encoding { return e.low } -// ExtractEncoding converts hard to navigate low-level plumbing Encoding definitions, into high-level simple map +// ExtractEncoding converts hard to navigate low-level plumbing Encoding definitions, into a high-level simple map func ExtractEncoding(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Encoding]) map[string]*Encoding { extracted := make(map[string]*Encoding) for k, v := range elements { diff --git a/datamodel/high/v3/link.go b/datamodel/high/v3/link.go index 31bf4ea..662e2ef 100644 --- a/datamodel/high/v3/link.go +++ b/datamodel/high/v3/link.go @@ -9,6 +9,16 @@ import ( ) // Link represents an OpenAPI 3+ Link object that is backed by a low-level one. +// +// The Link object represents a possible design-time link for a response. The presence of a link does not guarantee the +// caller’s ability to successfully invoke it, rather it provides a known relationship and traversal mechanism between +// responses and other operations. +// +// Unlike dynamic links (i.e. links provided in the response payload), the OAS linking mechanism does not require +// link information in the runtime response. +// +// For computing links, and providing instructions to execute them, a runtime expression is used for accessing values +// in an operation and using them as parameters while invoking the linked operation. // - https://spec.openapis.org/oas/v3.1.0#link-object type Link struct { OperationRef string diff --git a/datamodel/high/v3/media_type.go b/datamodel/high/v3/media_type.go index 68f9909..01a1186 100644 --- a/datamodel/high/v3/media_type.go +++ b/datamodel/high/v3/media_type.go @@ -12,6 +12,8 @@ import ( ) // MediaType represents a high-level OpenAPI MediaType object that is backed by a low-level one. +// +// Each Media Type Object provides schema and examples for the media type identified by its key. // - https://spec.openapis.org/oas/v3.1.0#media-type-object type MediaType struct { Schema *base.SchemaProxy @@ -22,6 +24,7 @@ type MediaType struct { low *low.MediaType } +// NewMediaType will create a new high-level MediaType instance from a low-level one. func NewMediaType(mediaType *low.MediaType) *MediaType { m := new(MediaType) m.low = mediaType @@ -35,10 +38,13 @@ func NewMediaType(mediaType *low.MediaType) *MediaType { return m } +// GoLow will return the low-level instance of MediaType used to create the high-level one. func (m *MediaType) GoLow() *low.MediaType { return m.low } +// ExtractContent takes in a complex and hard to navigate low-level content map, and converts it in to a much simpler +// and easier to navigate high-level one. func ExtractContent(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.MediaType]) map[string]*MediaType { // extract everything async doneChan := make(chan bool) diff --git a/datamodel/high/v3/oauth_flow.go b/datamodel/high/v3/oauth_flow.go index fa8896f..4345f9d 100644 --- a/datamodel/high/v3/oauth_flow.go +++ b/datamodel/high/v3/oauth_flow.go @@ -8,6 +8,8 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// OAuthFlow represents a high-level OpenAPI 3+ OAuthFlow object that is backed by a low-level one. +// - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object type OAuthFlow struct { AuthorizationUrl string TokenUrl string @@ -17,6 +19,7 @@ type OAuthFlow struct { low *low.OAuthFlow } +// NewOAuthFlow creates a new high-level OAuthFlow instance from a low-level one. func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow { o := new(OAuthFlow) o.low = flow @@ -32,6 +35,7 @@ func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow { return o } +// GoLow returns the low-level OAuthFlow instance used to create the high-level one. func (o *OAuthFlow) GoLow() *low.OAuthFlow { return o.low } diff --git a/datamodel/high/v3/oauth_flows.go b/datamodel/high/v3/oauth_flows.go index 87db4a0..64c3e46 100644 --- a/datamodel/high/v3/oauth_flows.go +++ b/datamodel/high/v3/oauth_flows.go @@ -8,6 +8,8 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// OAuthFlows represents a high-level OpenAPI 3+ OAuthFlows object that is backed by a low-level one. +// - https://spec.openapis.org/oas/v3.1.0#oauth-flows-object type OAuthFlows struct { Implicit *OAuthFlow Password *OAuthFlow @@ -17,6 +19,7 @@ type OAuthFlows struct { low *low.OAuthFlows } +// NewOAuthFlows creates a new high-level OAuthFlows instance from a low-level one. func NewOAuthFlows(flows *low.OAuthFlows) *OAuthFlows { o := new(OAuthFlows) o.low = flows @@ -39,6 +42,7 @@ func NewOAuthFlows(flows *low.OAuthFlows) *OAuthFlows { return o } +// GoLow returns the low-level OAuthFlows instance used to create the high-level one. func (o *OAuthFlows) GoLow() *low.OAuthFlows { return o.low } diff --git a/datamodel/high/v3/operation.go b/datamodel/high/v3/operation.go index 0350405..521232e 100644 --- a/datamodel/high/v3/operation.go +++ b/datamodel/high/v3/operation.go @@ -8,6 +8,10 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// Operation is a high-level representation of an OpenAPI 3+ Operation object, backed by a low-level one. +// An Operation is perhaps the most important object of the entire specification. Everything of value +// happens here. The entire being for existence of this library and the specification, is this Operation. +// - https://spec.openapis.org/oas/v3.1.0#operation-object type Operation struct { Tags []string Summary string @@ -25,6 +29,7 @@ type Operation struct { low *low.Operation } +// NewOperation will create a new Operation instance from a low-level one. func NewOperation(operation *low.Operation) *Operation { o := new(Operation) o.low = operation @@ -65,6 +70,7 @@ func NewOperation(operation *low.Operation) *Operation { return o } +// GoLow will return the low-level Operation instance that was used to create the high-level one. func (o *Operation) GoLow() *low.Operation { return o.low } diff --git a/datamodel/high/v3/parameter.go b/datamodel/high/v3/parameter.go index f05e2ef..1a9fdc9 100644 --- a/datamodel/high/v3/parameter.go +++ b/datamodel/high/v3/parameter.go @@ -9,6 +9,10 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// Parameter represents a high-level OpenAPI 3+ Parameter object, that is backed by a low-level one. +// +// A unique parameter is defined by a combination of a name and location. +// - https://spec.openapis.org/oas/v3.1.0#parameter-object type Parameter struct { Name string In string @@ -27,6 +31,7 @@ type Parameter struct { low *low.Parameter } +// NewParameter will create a new high-level instance of a Parameter, using a low-level one. func NewParameter(param *low.Parameter) *Parameter { p := new(Parameter) p.low = param @@ -49,6 +54,7 @@ func NewParameter(param *low.Parameter) *Parameter { return p } +// GoLow returns the low-level Parameter used to create the high-level one. func (p *Parameter) GoLow() *low.Parameter { return p.low } diff --git a/datamodel/high/v3/path_item.go b/datamodel/high/v3/path_item.go index e6c74f2..6ede15f 100644 --- a/datamodel/high/v3/path_item.go +++ b/datamodel/high/v3/path_item.go @@ -19,6 +19,12 @@ const ( trace ) +// PathItem represents a high-level OpenAPI 3+ PathItem object backed by a low-level one. +// +// 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. +// - https://spec.openapis.org/oas/v3.1.0#path-item-object type PathItem struct { Description string Summary string @@ -36,6 +42,7 @@ type PathItem struct { low *low.PathItem } +// NewPathItem creates a new high-level PathItem instance from a low-level one. func NewPathItem(pathItem *low.PathItem) *PathItem { pi := new(PathItem) pi.low = pathItem @@ -111,6 +118,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem { return pi } +// GoLow returns the low level instance of PathItem, used to build the high-level one. func (p *PathItem) GoLow() *low.PathItem { return p.low } diff --git a/datamodel/high/v3/paths.go b/datamodel/high/v3/paths.go index 9aa42b2..1ff9dad 100644 --- a/datamodel/high/v3/paths.go +++ b/datamodel/high/v3/paths.go @@ -8,12 +8,19 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// Paths represents a high-level OpenAPI 3+ Paths object, that is backed by a low-level one. +// +// Holds the relative paths to the individual endpoints and their operations. The path is appended to the URL from the +// Server Object in order to construct the full URL. The Paths MAY be empty, due to Access Control List (ACL) +// constraints. +// - https://spec.openapis.org/oas/v3.1.0#paths-object type Paths struct { PathItems map[string]*PathItem Extensions map[string]any low *low.Paths } +// NewPaths creates a new high-level instance of Paths from a low-level one. func NewPaths(paths *low.Paths) *Paths { p := new(Paths) p.low = paths @@ -44,6 +51,7 @@ func NewPaths(paths *low.Paths) *Paths { return p } +// GoLow returns the low-level Paths instance used to create the high-level one. func (p *Paths) GoLow() *low.Paths { return p.low } diff --git a/datamodel/high/v3/request_body.go b/datamodel/high/v3/request_body.go index ac8d508..476f270 100644 --- a/datamodel/high/v3/request_body.go +++ b/datamodel/high/v3/request_body.go @@ -8,6 +8,7 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// RequestBody represents a high-level OpenAPI 3+ RequestBody object, type RequestBody struct { Description string Content map[string]*MediaType diff --git a/datamodel/high/v3/response.go b/datamodel/high/v3/response.go index 73cad2f..dcbbafb 100644 --- a/datamodel/high/v3/response.go +++ b/datamodel/high/v3/response.go @@ -8,6 +8,11 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// Response represents a high-level OpenAPI 3+ Response object that is backed by a low-level one. +// +// Describes a single response from an API Operation, including design-time, static links to +// operations based on the response. +// - https://spec.openapis.org/oas/v3.1.0#response-object type Response struct { Description string Headers map[string]*Header @@ -17,6 +22,7 @@ type Response struct { low *low.Response } +// NewResponse creates a new high-level Response object that is backed by a low-level one. func NewResponse(response *low.Response) *Response { r := new(Response) r.low = response @@ -38,6 +44,7 @@ func NewResponse(response *low.Response) *Response { return r } +// GoLow returns the low-level Response object that was used to create the high-level one. func (r *Response) GoLow() *low.Response { return r.low } diff --git a/datamodel/high/v3/responses.go b/datamodel/high/v3/responses.go index f4e1db5..3888cab 100644 --- a/datamodel/high/v3/responses.go +++ b/datamodel/high/v3/responses.go @@ -8,12 +8,28 @@ import ( low "github.com/pb33f/libopenapi/datamodel/low/v3" ) +// Responses represents a high-level OpenAPI 3+ Responses object that is backed by a low-level one. +// +// It's a container for the expected responses of an operation. The container maps a HTTP response code to the +// expected response. +// +// The specification is not necessarily expected to cover all possible HTTP response codes because they may not be +// known in advance. However, documentation is expected to cover a successful operation response and any known errors. +// +// The default MAY be used as a default response object for all HTTP codes that are not covered individually by +// the Responses Object. +// +// The Responses Object MUST contain at least one response code, and if only one response code is provided it SHOULD +// be the response for a successful operation call. +// - https://spec.openapis.org/oas/v3.1.0#responses-object type Responses struct { Codes map[string]*Response Default *Response low *low.Responses } +// NewResponses will create a new high-level Responses instance from a low-level one. It operates asynchronously +// internally, as each response may be considerable in complexity. func NewResponses(response *low.Responses) *Responses { r := new(Responses) r.low = response @@ -49,10 +65,12 @@ func NewResponses(response *low.Responses) *Responses { return r } +// FindResponseByCode is a shortcut for looking up code by an integer vs. a string func (r *Responses) FindResponseByCode(code int) *Response { return r.Codes[fmt.Sprintf("%d", code)] } +// GoLow returns the low-level Response object used to create the high-level one. func (r *Responses) GoLow() *low.Responses { return r.low } diff --git a/datamodel/high/v3/security_requirement.go b/datamodel/high/v3/security_requirement.go index ac24235..cb98edd 100644 --- a/datamodel/high/v3/security_requirement.go +++ b/datamodel/high/v3/security_requirement.go @@ -5,11 +5,24 @@ package v3 import low "github.com/pb33f/libopenapi/datamodel/low/v3" +// SecurityRequirement is a high-level representation of an OpenAPI 3+ SecurityRequirement object that is backed +// by a low-level one. +// +// It lists the required security schemes to execute this operation. The name used for each property MUST correspond +// to a security scheme declared in the Security Schemes under the Components Object. +// +// Security Requirement Objects that contain multiple schemes require that all schemes MUST be satisfied for a +// request to be authorized. This enables support for scenarios where multiple query parameters or HTTP headers are required to convey security information. +// +// When a list of Security Requirement Objects is defined on the OpenAPI Object or Operation Object, only one of the +// Security Requirement Objects in the list needs to be satisfied to authorize the request. +// - https://spec.openapis.org/oas/v3.1.0#security-requirement-object type SecurityRequirement struct { ValueRequirements []map[string][]string low *low.SecurityRequirement } +// NewSecurityRequirement will create a new high-level SecurityRequirement instance, from a low-level one. func NewSecurityRequirement(req *low.SecurityRequirement) *SecurityRequirement { r := new(SecurityRequirement) r.low = req @@ -29,6 +42,7 @@ func NewSecurityRequirement(req *low.SecurityRequirement) *SecurityRequirement { return r } +// GoLow returns the low-level SecurityRequirement instance used to create the high-level one. func (s *SecurityRequirement) GoLow() *low.SecurityRequirement { return s.low }