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.
This commit is contained in:
Dave Shanley
2022-09-17 09:57:29 -04:00
parent c17cc4a7e6
commit 649a93b008
17 changed files with 123 additions and 7 deletions

View File

@@ -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. [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) 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. **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 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. specification low level details.
Want to build a linter? Analysis tool? Renderer that retains original positions?
## libopenapi retains _everything_. ## libopenapi retains _everything_.
libopenapi has been designed to retain all of that really low-level detail about the AST, line numbers, column numbers, comments, 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 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. underlying raw data backing that model.
This library exists because this very need existed inside [VMware](https://vmware.com), we built our own internal 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 bespoke. 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 libopenapi is the result of years of learning and battle testing OpenAPI in golang. This library represents what would
would have created, if we knew then - what we know now. 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** > 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() v3Model, _ := document.BuildV3Model()
// extract the RequestBody from the 'put' operation under the /pet path // 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 // dropdown to the low-level API for RequestBody
lowReqBody := reqBody.GoLow() lowReqBody := reqBody.GoLow()

View File

@@ -8,6 +8,10 @@ import (
) )
// Info represents an Info object as defined by both OpenAPI 2 and OpenAPI 3. // 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 // v2 - https://swagger.io/specification/v2/#infoObject
// v3 - https://spec.openapis.org/oas/v3.1.0#info-object // v3 - https://spec.openapis.org/oas/v3.1.0#info-object
type Info struct { type Info struct {

View File

@@ -6,6 +6,11 @@ package v3
import low "github.com/pb33f/libopenapi/datamodel/low/v3" import low "github.com/pb33f/libopenapi/datamodel/low/v3"
// Callback represents a high-level Callback object for OpenAPI 3+. // 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 // - https://spec.openapis.org/oas/v3.1.0#callback-object
type Callback struct { type Callback struct {
Expression map[string]*PathItem Expression map[string]*PathItem

View File

@@ -11,6 +11,7 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// used for internal channel co-ordination for building out different component types.
const ( const (
responses = iota responses = iota
parameters parameters
@@ -22,6 +23,11 @@ const (
callbacks 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 { type Components struct {
Schemas map[string]*highbase.SchemaProxy Schemas map[string]*highbase.SchemaProxy
Responses map[string]*Response Responses map[string]*Response
@@ -36,6 +42,9 @@ type Components struct {
low *low.Components 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 { func NewComponents(comp *low.Components) *Components {
c := new(Components) c := new(Components)
c.low = comp c.low = comp
@@ -140,16 +149,19 @@ func NewComponents(comp *low.Components) *Components {
return c return c
} }
// contains a component build result.
type componentResult[T any] struct { type componentResult[T any] struct {
res T res T
key string key string
comp int 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) { 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} 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]) { func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*base.SchemaProxy], c chan componentResult[*highbase.SchemaProxy]) {
var sch *highbase.SchemaProxy var sch *highbase.SchemaProxy
sch = highbase.NewSchemaProxy(&lowmodel.NodeReference[*base.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} 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 { func (c *Components) GoLow() *low.Components {
return c.low return c.low
} }

View File

@@ -36,7 +36,7 @@ func (e *Encoding) GoLow() *low.Encoding {
return e.low 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 { func ExtractEncoding(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Encoding]) map[string]*Encoding {
extracted := make(map[string]*Encoding) extracted := make(map[string]*Encoding)
for k, v := range elements { for k, v := range elements {

View File

@@ -9,6 +9,16 @@ import (
) )
// Link represents an OpenAPI 3+ Link object that is backed by a low-level one. // 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
// callers 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 // - https://spec.openapis.org/oas/v3.1.0#link-object
type Link struct { type Link struct {
OperationRef string OperationRef string

View File

@@ -12,6 +12,8 @@ import (
) )
// MediaType represents a high-level OpenAPI MediaType object that is backed by a low-level one. // 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 // - https://spec.openapis.org/oas/v3.1.0#media-type-object
type MediaType struct { type MediaType struct {
Schema *base.SchemaProxy Schema *base.SchemaProxy
@@ -22,6 +24,7 @@ type MediaType struct {
low *low.MediaType low *low.MediaType
} }
// NewMediaType will create a new high-level MediaType instance from a low-level one.
func NewMediaType(mediaType *low.MediaType) *MediaType { func NewMediaType(mediaType *low.MediaType) *MediaType {
m := new(MediaType) m := new(MediaType)
m.low = mediaType m.low = mediaType
@@ -35,10 +38,13 @@ func NewMediaType(mediaType *low.MediaType) *MediaType {
return m return m
} }
// GoLow will return the low-level instance of MediaType used to create the high-level one.
func (m *MediaType) GoLow() *low.MediaType { func (m *MediaType) GoLow() *low.MediaType {
return m.low 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 { func ExtractContent(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.MediaType]) map[string]*MediaType {
// extract everything async // extract everything async
doneChan := make(chan bool) doneChan := make(chan bool)

View File

@@ -8,6 +8,8 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" 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 { type OAuthFlow struct {
AuthorizationUrl string AuthorizationUrl string
TokenUrl string TokenUrl string
@@ -17,6 +19,7 @@ type OAuthFlow struct {
low *low.OAuthFlow low *low.OAuthFlow
} }
// NewOAuthFlow creates a new high-level OAuthFlow instance from a low-level one.
func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow { func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow {
o := new(OAuthFlow) o := new(OAuthFlow)
o.low = flow o.low = flow
@@ -32,6 +35,7 @@ func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow {
return o return o
} }
// GoLow returns the low-level OAuthFlow instance used to create the high-level one.
func (o *OAuthFlow) GoLow() *low.OAuthFlow { func (o *OAuthFlow) GoLow() *low.OAuthFlow {
return o.low return o.low
} }

View File

@@ -8,6 +8,8 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" 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 { type OAuthFlows struct {
Implicit *OAuthFlow Implicit *OAuthFlow
Password *OAuthFlow Password *OAuthFlow
@@ -17,6 +19,7 @@ type OAuthFlows struct {
low *low.OAuthFlows low *low.OAuthFlows
} }
// NewOAuthFlows creates a new high-level OAuthFlows instance from a low-level one.
func NewOAuthFlows(flows *low.OAuthFlows) *OAuthFlows { func NewOAuthFlows(flows *low.OAuthFlows) *OAuthFlows {
o := new(OAuthFlows) o := new(OAuthFlows)
o.low = flows o.low = flows
@@ -39,6 +42,7 @@ func NewOAuthFlows(flows *low.OAuthFlows) *OAuthFlows {
return o return o
} }
// GoLow returns the low-level OAuthFlows instance used to create the high-level one.
func (o *OAuthFlows) GoLow() *low.OAuthFlows { func (o *OAuthFlows) GoLow() *low.OAuthFlows {
return o.low return o.low
} }

View File

@@ -8,6 +8,10 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" 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 { type Operation struct {
Tags []string Tags []string
Summary string Summary string
@@ -25,6 +29,7 @@ type Operation struct {
low *low.Operation low *low.Operation
} }
// NewOperation will create a new Operation instance from a low-level one.
func NewOperation(operation *low.Operation) *Operation { func NewOperation(operation *low.Operation) *Operation {
o := new(Operation) o := new(Operation)
o.low = operation o.low = operation
@@ -65,6 +70,7 @@ func NewOperation(operation *low.Operation) *Operation {
return o 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 { func (o *Operation) GoLow() *low.Operation {
return o.low return o.low
} }

View File

@@ -9,6 +9,10 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" 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 { type Parameter struct {
Name string Name string
In string In string
@@ -27,6 +31,7 @@ type Parameter struct {
low *low.Parameter 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 { func NewParameter(param *low.Parameter) *Parameter {
p := new(Parameter) p := new(Parameter)
p.low = param p.low = param
@@ -49,6 +54,7 @@ func NewParameter(param *low.Parameter) *Parameter {
return p return p
} }
// GoLow returns the low-level Parameter used to create the high-level one.
func (p *Parameter) GoLow() *low.Parameter { func (p *Parameter) GoLow() *low.Parameter {
return p.low return p.low
} }

View File

@@ -19,6 +19,12 @@ const (
trace 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 { type PathItem struct {
Description string Description string
Summary string Summary string
@@ -36,6 +42,7 @@ type PathItem struct {
low *low.PathItem low *low.PathItem
} }
// NewPathItem creates a new high-level PathItem instance from a low-level one.
func NewPathItem(pathItem *low.PathItem) *PathItem { func NewPathItem(pathItem *low.PathItem) *PathItem {
pi := new(PathItem) pi := new(PathItem)
pi.low = pathItem pi.low = pathItem
@@ -111,6 +118,7 @@ func NewPathItem(pathItem *low.PathItem) *PathItem {
return pi return pi
} }
// GoLow returns the low level instance of PathItem, used to build the high-level one.
func (p *PathItem) GoLow() *low.PathItem { func (p *PathItem) GoLow() *low.PathItem {
return p.low return p.low
} }

View File

@@ -8,12 +8,19 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" 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 { type Paths struct {
PathItems map[string]*PathItem PathItems map[string]*PathItem
Extensions map[string]any Extensions map[string]any
low *low.Paths low *low.Paths
} }
// NewPaths creates a new high-level instance of Paths from a low-level one.
func NewPaths(paths *low.Paths) *Paths { func NewPaths(paths *low.Paths) *Paths {
p := new(Paths) p := new(Paths)
p.low = paths p.low = paths
@@ -44,6 +51,7 @@ func NewPaths(paths *low.Paths) *Paths {
return p return p
} }
// GoLow returns the low-level Paths instance used to create the high-level one.
func (p *Paths) GoLow() *low.Paths { func (p *Paths) GoLow() *low.Paths {
return p.low return p.low
} }

View File

@@ -8,6 +8,7 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" low "github.com/pb33f/libopenapi/datamodel/low/v3"
) )
// RequestBody represents a high-level OpenAPI 3+ RequestBody object,
type RequestBody struct { type RequestBody struct {
Description string Description string
Content map[string]*MediaType Content map[string]*MediaType

View File

@@ -8,6 +8,11 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" 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 { type Response struct {
Description string Description string
Headers map[string]*Header Headers map[string]*Header
@@ -17,6 +22,7 @@ type Response struct {
low *low.Response 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 { func NewResponse(response *low.Response) *Response {
r := new(Response) r := new(Response)
r.low = response r.low = response
@@ -38,6 +44,7 @@ func NewResponse(response *low.Response) *Response {
return r return r
} }
// GoLow returns the low-level Response object that was used to create the high-level one.
func (r *Response) GoLow() *low.Response { func (r *Response) GoLow() *low.Response {
return r.low return r.low
} }

View File

@@ -8,12 +8,28 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/v3" 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 { type Responses struct {
Codes map[string]*Response Codes map[string]*Response
Default *Response Default *Response
low *low.Responses 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 { func NewResponses(response *low.Responses) *Responses {
r := new(Responses) r := new(Responses)
r.low = response r.low = response
@@ -49,10 +65,12 @@ func NewResponses(response *low.Responses) *Responses {
return r return r
} }
// FindResponseByCode is a shortcut for looking up code by an integer vs. a string
func (r *Responses) FindResponseByCode(code int) *Response { func (r *Responses) FindResponseByCode(code int) *Response {
return r.Codes[fmt.Sprintf("%d", code)] 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 { func (r *Responses) GoLow() *low.Responses {
return r.low return r.low
} }

View File

@@ -5,11 +5,24 @@ package v3
import low "github.com/pb33f/libopenapi/datamodel/low/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 { type SecurityRequirement struct {
ValueRequirements []map[string][]string ValueRequirements []map[string][]string
low *low.SecurityRequirement low *low.SecurityRequirement
} }
// NewSecurityRequirement will create a new high-level SecurityRequirement instance, from a low-level one.
func NewSecurityRequirement(req *low.SecurityRequirement) *SecurityRequirement { func NewSecurityRequirement(req *low.SecurityRequirement) *SecurityRequirement {
r := new(SecurityRequirement) r := new(SecurityRequirement)
r.low = req r.low = req
@@ -29,6 +42,7 @@ func NewSecurityRequirement(req *low.SecurityRequirement) *SecurityRequirement {
return r return r
} }
// GoLow returns the low-level SecurityRequirement instance used to create the high-level one.
func (s *SecurityRequirement) GoLow() *low.SecurityRequirement { func (s *SecurityRequirement) GoLow() *low.SecurityRequirement {
return s.low return s.low
} }