V2 low-base docs in place

refactored constants a little also to clean things up.
This commit is contained in:
Dave Shanley
2022-09-20 10:08:31 -04:00
parent 65b242b6c4
commit be71a5e775
16 changed files with 674 additions and 512 deletions

View File

@@ -9,7 +9,7 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
) )
// Example represents an Example object as defined by OpenAPI 3+ // Example represents a high-level Example object as defined by OpenAPI 3+
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object // v3 - https://spec.openapis.org/oas/v3.1.0#example-object
type Example struct { type Example struct {
Summary string Summary string

View File

@@ -8,7 +8,7 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
) )
// ExternalDoc represents an External Documentation object as defined by OpenAPI 2 and 3 // ExternalDoc represents a high-level External Documentation object as defined by OpenAPI 2 and 3
// //
// Allows referencing an external resource for extended documentation. // Allows referencing an external resource for extended documentation.
// v2 - https://swagger.io/specification/v2/#externalDocumentationObject // v2 - https://swagger.io/specification/v2/#externalDocumentationObject

View File

@@ -7,7 +7,7 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
) )
// Info represents an Info object as defined by both OpenAPI 2 and OpenAPI 3. // Info represents a high-level 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 // 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. // in editing or documentation generation tools for convenience.

View File

@@ -7,7 +7,7 @@ import (
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
) )
// License is a representation of a License object as defined by OpenAPI 2 and OpenAPI 3 // License is a high-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3
// v2 - https://swagger.io/specification/v2/#licenseObject // v2 - https://swagger.io/specification/v2/#licenseObject
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object // v3 - https://spec.openapis.org/oas/v3.1.0#license-object
type License struct { type License struct {

View File

@@ -0,0 +1,11 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
// Package base contains shared low-level models that are used between both versions 2 and 3 of OpenAPI.
// These models are consistent across both specifications, except for the Schema.
//
// OpenAPI 3 contains all the same properties that an OpenAPI 2 specification does, and more. The choice
// to not duplicate the schemas is to allow a graceful degradation pattern to be used. Schemas are the most complex
// beats, particularly when polymorphism is used. By re-using the same superset Schema across versions, we can ensure
// that all the latest features are collected, without damaging backwards compatibility.
package base

View File

@@ -0,0 +1,30 @@
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package base
// Constants for labels used to look up values within OpenAPI specifications.
const (
TagsLabel = "tags"
ExternalDocsLabel = "externalDocs"
ExamplesLabel = "examples"
ExampleLabel = "example"
ValueLabel = "value"
InfoLabel = "info"
ContactLabel = "contact"
LicenseLabel = "license"
PropertiesLabel = "properties"
AdditionalPropertiesLabel = "additionalProperties"
XMLLabel = "xml"
ItemsLabel = "items"
AllOfLabel = "allOf"
AnyOfLabel = "anyOf"
OneOfLabel = "oneOf"
NotLabel = "not"
TypeLabel = "type"
DiscriminatorLabel = "discriminator"
ExclusiveMinimumLabel = "exclusiveMinimum"
ExclusiveMaximumLabel = "exclusiveMaximum"
SchemaLabel = "schema"
SchemaTypeLabel = "$schema"
)

View File

@@ -9,12 +9,16 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// Contact represents a low-level representation of the Contact definitions found at
// v2 - https://swagger.io/specification/v2/#contactObject
// v3 - https://spec.openapis.org/oas/v3.1.0#contact-object
type Contact struct { type Contact struct {
Name low.NodeReference[string] Name low.NodeReference[string]
URL low.NodeReference[string] URL low.NodeReference[string]
Email low.NodeReference[string] Email low.NodeReference[string]
} }
// Build is not implemented for Contact (there is nothing to build).
func (c *Contact) Build(root *yaml.Node, idx *index.SpecIndex) error { func (c *Contact) Build(root *yaml.Node, idx *index.SpecIndex) error {
// not implemented. // not implemented.
return nil return nil

View File

@@ -7,11 +7,20 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
) )
// Discriminator is only used by OpenAPI 3+ documents, it represents a polymorphic discriminator used for schemas
//
// When request bodies or response payloads may be one of a number of different schemas, a discriminator object can be
// used to aid in serialization, deserialization, and validation. The discriminator is a specific object in a schema
// which is used to inform the consumer of the document of an alternative schema based on the value associated with it.
//
// When using the discriminator, inline schemas will not be considered.
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName low.NodeReference[string] PropertyName low.NodeReference[string]
Mapping map[low.KeyReference[string]]low.ValueReference[string] Mapping map[low.KeyReference[string]]low.ValueReference[string]
} }
// FindMappingValue will return a ValueReference containing the string mapping value
func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string] { func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string] {
for k, v := range d.Mapping { for k, v := range d.Mapping {
if k.Value == key { if k.Value == key {

View File

@@ -11,12 +11,8 @@ import (
"strconv" "strconv"
) )
const ( // Example represents a low-level Example object as defined by OpenAPI 3+
ExamplesLabel = "examples" // v3 - https://spec.openapis.org/oas/v3.1.0#example-object
ExampleLabel = "example"
ValueLabel = "value"
)
type Example struct { type Example struct {
Summary low.NodeReference[string] Summary low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]
@@ -25,10 +21,12 @@ type Example struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
// FindExtension returns a ValueReference containing the extension value, if found.
func (ex *Example) FindExtension(ext string) *low.ValueReference[any] { func (ex *Example) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, ex.Extensions) return low.FindItemInMap[any](ext, ex.Extensions)
} }
// Build extracts extensions and example value
func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error { func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error {
ex.Extensions = low.ExtractExtensions(root) ex.Extensions = low.ExtractExtensions(root)
_, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content) _, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content)
@@ -68,6 +66,7 @@ func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error {
return nil return nil
} }
// ExtractExampleValue will extract a primitive example value (if possible), or just the raw Value property if not.
func ExtractExampleValue(exp *yaml.Node) any { func ExtractExampleValue(exp *yaml.Node) any {
if utils.IsNodeBoolValue(exp) { if utils.IsNodeBoolValue(exp) {
v, _ := strconv.ParseBool(exp.Value) v, _ := strconv.ParseBool(exp.Value)

View File

@@ -9,16 +9,23 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// ExternalDoc represents a low-level External Documentation object as defined by OpenAPI 2 and 3
//
// Allows referencing an external resource for extended documentation.
// v2 - https://swagger.io/specification/v2/#externalDocumentationObject
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object
type ExternalDoc struct { type ExternalDoc struct {
Description low.NodeReference[string] Description low.NodeReference[string]
URL low.NodeReference[string] URL low.NodeReference[string]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
// FindExtension returns a ValueReference containing the extension value, if found.
func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] { func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, ex.Extensions) return low.FindItemInMap[any](ext, ex.Extensions)
} }
// Build will extract extensions from the ExternalDoc instance.
func (ex *ExternalDoc) Build(root *yaml.Node, idx *index.SpecIndex) error { func (ex *ExternalDoc) Build(root *yaml.Node, idx *index.SpecIndex) error {
ex.Extensions = low.ExtractExtensions(root) ex.Extensions = low.ExtractExtensions(root)
return nil return nil

View File

@@ -9,12 +9,13 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const ( // Info represents a low-level Info object as defined by both OpenAPI 2 and OpenAPI 3.
InfoLabel = "info" //
ContactLabel = "contact" // The object provides metadata about the API. The metadata MAY be used by the clients if needed, and MAY be presented
LicenseLabel = "license" // 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 { type Info struct {
Title low.NodeReference[string] Title low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]
@@ -24,6 +25,7 @@ type Info struct {
Version low.NodeReference[string] Version low.NodeReference[string]
} }
// Build will extract out the Contact and Info objects from the supplied root node.
func (i *Info) Build(root *yaml.Node, idx *index.SpecIndex) error { func (i *Info) Build(root *yaml.Node, idx *index.SpecIndex) error {
// extract contact // extract contact
contact, _ := low.ExtractObject[*Contact](ContactLabel, root, idx) contact, _ := low.ExtractObject[*Contact](ContactLabel, root, idx)

View File

@@ -9,11 +9,15 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// License is a low-level representation of a License object as defined by OpenAPI 2 and OpenAPI 3
// v2 - https://swagger.io/specification/v2/#licenseObject
// v3 - https://spec.openapis.org/oas/v3.1.0#license-object
type License struct { type License struct {
Name low.NodeReference[string] Name low.NodeReference[string]
URL low.NodeReference[string] URL low.NodeReference[string]
} }
// Build is not implemented for License (there is nothing to build)
func (l *License) Build(root *yaml.Node, idx *index.SpecIndex) error { func (l *License) Build(root *yaml.Node, idx *index.SpecIndex) error {
return nil return nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,38 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// SchemaProxy exists as a stub that will create a Schema once (and only once) the Schema() method is called.
//
// Why use a Proxy design?
//
// There are three reasons.
//
// 1. Circular References and Endless Loops.
//
// JSON Schema allows for references to be used. This means references can loop around and create infinite recursive
// structures, These 'Circular references' technically mean a schema can NEVER be resolved, not without breaking the
// loop somewhere along the chain.
//
// Polymorphism in the form of 'oneOf' and 'anyOf' in version 3+ only exacerbates the problem.
//
// These circular traps can be discovered using the resolver, however it's still not enough to stop endless loops and
// endless goroutine spawning. A proxy design means that resolving occurs on demand and runs down a single level only.
// preventing any run-away loops.
//
// 2. Performance
//
// Even without circular references, Polymorphism creates large additional resolving chains that take a long time
// and slow things down when building. By preventing recursion through every polymorphic item, building models is kept
// fast and snappy, which is desired for realtime processing of specs.
//
// - Q: Yeah, but, why not just use state to avoiding re-visiting seen polymorphic nodes?
// - A: It's slow, takes up memory and still has runaway potential in very, very long chains.
//
// 3. Short Circuit Errors.
//
// Schemas are where things can get messy, mainly because the Schema standard changes between versions, and
// it's not actually JSONSchema until 3.1, so lots of times a bad schema will break parsing. Errors are only found
// when a schema is needed, so the rest of the document is parsed and ready to use.
type SchemaProxy struct { type SchemaProxy struct {
kn *yaml.Node kn *yaml.Node
vn *yaml.Node vn *yaml.Node
@@ -17,12 +49,24 @@ type SchemaProxy struct {
buildError error buildError error
} }
// Build will prepare the SchemaProxy for rendering, it does not build the Schema, only sets up internal state.
func (sp *SchemaProxy) Build(root *yaml.Node, idx *index.SpecIndex) error { func (sp *SchemaProxy) Build(root *yaml.Node, idx *index.SpecIndex) error {
sp.vn = root sp.vn = root
sp.idx = idx sp.idx = idx
return nil return nil
} }
// Schema will first check if this SchemaProxy has already rendered the schema, and return the pre-rendered version
// first.
//
// If this is the first run of Schema(), then the SchemaProxy will create a new Schema from the underlying
// yaml.Node. Once built out, the SchemaProxy will record that Schema as rendered and store it for later use,
// (this is what is we mean when we say 'pre-rendered').
//
// Schema() then returns the newly created Schema.
//
// If anything goes wrong during the build, then nothing is returned and the error that occurred can
// be retrieved by using GetBuildError()
func (sp *SchemaProxy) Schema() *Schema { func (sp *SchemaProxy) Schema() *Schema {
if sp.rendered != nil { if sp.rendered != nil {
return sp.rendered return sp.rendered
@@ -31,10 +75,6 @@ func (sp *SchemaProxy) Schema() *Schema {
_ = low.BuildModel(sp.vn, schema) _ = low.BuildModel(sp.vn, schema)
err := schema.Build(sp.vn, sp.idx) err := schema.Build(sp.vn, sp.idx)
if err != nil { if err != nil {
//low.Log.Error("unable to build schema",
// zap.Int("line", sp.vn.Line),
// zap.Int("column", sp.vn.Column),
// zap.String("error", err.Error()))
sp.buildError = err sp.buildError = err
return nil return nil
} }
@@ -42,6 +82,8 @@ func (sp *SchemaProxy) Schema() *Schema {
return schema return schema
} }
// GetBuildError returns the build error that was set when Schema() was called. If Schema() has not been run, or
// there were no errors during build, then nil will be returned.
func (sp *SchemaProxy) GetBuildError() error { func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError return sp.buildError
} }

View File

@@ -9,13 +9,12 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const ( // Tag represents a low-level Tag instance that is backed by a low-level one.
TagsLabel = "tags" //
ExternalDocsLabel = "externalDocs" // Adds metadata to a single tag that is used by the Operation Object. It is not mandatory to have a Tag Object per
NameLabel = "name" // tag defined in the Operation Object instances.
DescriptionLabel = "description" // - v2: https://swagger.io/specification/v2/#tagObject
) // - v3: https://swagger.io/specification/#tag-object
type Tag struct { type Tag struct {
Name low.NodeReference[string] Name low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]
@@ -23,10 +22,12 @@ type Tag struct {
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
// FindExtension returns a ValueReference containing the extension value, if found.
func (t *Tag) FindExtension(ext string) *low.ValueReference[any] { func (t *Tag) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, t.Extensions) return low.FindItemInMap[any](ext, t.Extensions)
} }
// Build will extract extensions and external docs for the Tag.
func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error { func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error {
t.Extensions = low.ExtractExtensions(root) t.Extensions = low.ExtractExtensions(root)
@@ -36,6 +37,7 @@ func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error {
return err return err
} }
// TODO: future mutation API experiment code is here. this snippet is to re-marshal the object.
//func (t *Tag) MarshalYAML() (interface{}, error) { //func (t *Tag) MarshalYAML() (interface{}, error) {
// m := make(map[string]interface{}) // m := make(map[string]interface{})
// for i := range t.Extensions { // for i := range t.Extensions {

View File

@@ -1,21 +1,30 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// XML represents a low-level representation of an XML object defined by all versions of OpenAPI.
//
// A metadata object that allows for more fine-tuned XML model definitions.
//
// When using arrays, XML element names are not inferred (for singular/plural forms) and the name property SHOULD be
// used to add that information. See examples for expected behavior.
// v2 - https://swagger.io/specification/v2/#xmlObject
// v3 - https://swagger.io/specification/#xml-object
type XML struct { type XML struct {
Name low.NodeReference[string] Name low.NodeReference[string]
Namespace low.NodeReference[string] Namespace low.NodeReference[string]
Prefix low.NodeReference[string] Prefix low.NodeReference[string]
Attribute low.NodeReference[bool] Attribute low.NodeReference[bool]
Wrapped low.NodeReference[bool] Wrapped low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
// Build will extract extensions from the XML instance.
func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error { func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
x.Extensions = low.ExtractExtensions(root) x.Extensions = low.ExtractExtensions(root)
return nil return nil
} }