mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-08 04:20:17 +00:00
High-level base documentation is complete.
Examples and every model completed, 1/6th of the way through models.
This commit is contained in:
@@ -10,17 +10,55 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Schema represents a
|
||||
// Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1
|
||||
//
|
||||
// Until 3.1 OpenAPI had a strange relationship with JSON Schema. It's been a super-set/sub-set
|
||||
// mix, which has been confusing. So, instead of building a bunch of different models, we have compressed
|
||||
// all variations into a single model that makes it easy to support multiple spec types.
|
||||
//
|
||||
// - v2 schema: https://swagger.io/specification/v2/#schemaObject
|
||||
// - v3 schema: https://swagger.io/specification/#schema-object
|
||||
// - v3.1 schema: https://spec.openapis.org/oas/v3.1.0#schema-object
|
||||
type Schema struct {
|
||||
|
||||
// 3.1 only, used to define a dialect for this schema, label is '$schema'.
|
||||
SchemaTypeRef string
|
||||
|
||||
// In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean.
|
||||
ExclusiveMaximumBool bool
|
||||
|
||||
// In version 3.1, ExclusiveMaximum is an integer.
|
||||
ExclusiveMaximum int64
|
||||
|
||||
// In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean.
|
||||
ExclusiveMinimum int64
|
||||
|
||||
// In version 3.1, ExclusiveMinimum is an integer.
|
||||
ExclusiveMinimumBool bool
|
||||
|
||||
// In versions 2 and 3.0, this Type is a single value, so array will only ever have one value
|
||||
// in version 3.1, Type can be multiple values
|
||||
Type []string
|
||||
|
||||
// Schemas are resolved on demand using a SchemaProxy
|
||||
AllOf []*SchemaProxy
|
||||
|
||||
// Polymorphic Schemas are only available in version 3+
|
||||
OneOf []*SchemaProxy
|
||||
AnyOf []*SchemaProxy
|
||||
Discriminator *Discriminator
|
||||
|
||||
// in 3.1 examples can be an array (which is recommended)
|
||||
Examples []any
|
||||
|
||||
// Compatible with all versions
|
||||
Not []*SchemaProxy
|
||||
Items []*SchemaProxy
|
||||
Properties map[string]*SchemaProxy
|
||||
Title string
|
||||
MultipleOf int64
|
||||
Maximum int64
|
||||
ExclusiveMaximumBool bool
|
||||
ExclusiveMaximum int64
|
||||
Minimum int64
|
||||
ExclusiveMinimum int64
|
||||
ExclusiveMinimumBool bool
|
||||
MaxLength int64
|
||||
MinLength int64
|
||||
Pattern string
|
||||
@@ -32,29 +70,21 @@ type Schema struct {
|
||||
MinProperties int64
|
||||
Required []string
|
||||
Enum []string
|
||||
Type []string
|
||||
AllOf []*SchemaProxy
|
||||
OneOf []*SchemaProxy
|
||||
AnyOf []*SchemaProxy
|
||||
Not []*SchemaProxy
|
||||
Items []*SchemaProxy
|
||||
Properties map[string]*SchemaProxy
|
||||
AdditionalProperties any
|
||||
Description string
|
||||
Default any
|
||||
Nullable bool
|
||||
Discriminator *Discriminator
|
||||
ReadOnly bool
|
||||
WriteOnly bool
|
||||
XML *XML
|
||||
ExternalDocs *ExternalDoc
|
||||
Example any
|
||||
Examples []any
|
||||
Deprecated bool
|
||||
Extensions map[string]any
|
||||
low *base.Schema
|
||||
}
|
||||
|
||||
// NewSchema will create a new high-level schema from a low-level one.
|
||||
func NewSchema(schema *base.Schema) *Schema {
|
||||
s := new(Schema)
|
||||
s.low = schema
|
||||
@@ -237,6 +267,7 @@ func NewSchema(schema *base.Schema) *Schema {
|
||||
return s
|
||||
}
|
||||
|
||||
// GoLow will return the low-level instance of Schema that was used to create the high level one.
|
||||
func (s *Schema) GoLow() *base.Schema {
|
||||
return s.low
|
||||
}
|
||||
|
||||
@@ -8,15 +8,52 @@ import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
)
|
||||
|
||||
// SchemaProxy exists as a stub that will create a Schema once (and only once) the Schema() method is called. An
|
||||
// underlying low-level SchemaProxy backs this high-level one.
|
||||
//
|
||||
// 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 {
|
||||
schema *low.NodeReference[*base.SchemaProxy]
|
||||
buildError error
|
||||
}
|
||||
|
||||
// NewSchemaProxy creates a new high-level SchemaProxy from a low-level one.
|
||||
func NewSchemaProxy(schema *low.NodeReference[*base.SchemaProxy]) *SchemaProxy {
|
||||
return &SchemaProxy{schema: schema}
|
||||
}
|
||||
|
||||
// Schema will create a new Schema instance using NewSchema from the low-level SchemaProxy backing this high-level one.
|
||||
// If there is a problem building the Schema, then this method will return nil. Use GetBuildError to gain access
|
||||
// to that building error.
|
||||
func (sp *SchemaProxy) Schema() *Schema {
|
||||
s := sp.schema.Value.Schema()
|
||||
if s == nil {
|
||||
@@ -26,6 +63,7 @@ func (sp *SchemaProxy) Schema() *Schema {
|
||||
return NewSchema(s)
|
||||
}
|
||||
|
||||
// GetBuildError returns any error that was thrown when calling Schema()
|
||||
func (sp *SchemaProxy) GetBuildError() error {
|
||||
return sp.buildError
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
@@ -326,3 +327,67 @@ required: [cake, fish]`
|
||||
assert.Equal(t, 100, wentLower.Name.ValueNode.Line)
|
||||
|
||||
}
|
||||
|
||||
func ExampleNewSchema() {
|
||||
|
||||
// create an example schema object
|
||||
// this can be either JSON or YAML.
|
||||
yml := `
|
||||
title: this is a schema
|
||||
type: object
|
||||
properties:
|
||||
aProperty:
|
||||
description: this is an integer property
|
||||
type: integer
|
||||
format: int64`
|
||||
|
||||
// unmarshal raw bytes
|
||||
var node yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &node)
|
||||
|
||||
// build out the low-level model
|
||||
var lowSchema lowbase.Schema
|
||||
_ = low.BuildModel(&node, &lowSchema)
|
||||
_ = lowSchema.Build(node.Content[0], nil)
|
||||
|
||||
// build the high level model
|
||||
highSchema := NewSchema(&lowSchema)
|
||||
|
||||
// print out the description of 'aProperty'
|
||||
fmt.Print(highSchema.Properties["aProperty"].Schema().Description)
|
||||
// Output: this is an integer property
|
||||
|
||||
}
|
||||
|
||||
func ExampleNewSchemaProxy() {
|
||||
|
||||
// create an example schema object
|
||||
// this can be either JSON or YAML.
|
||||
yml := `
|
||||
title: this is a schema
|
||||
type: object
|
||||
properties:
|
||||
aProperty:
|
||||
description: this is an integer property
|
||||
type: integer
|
||||
format: int64`
|
||||
|
||||
// unmarshal raw bytes
|
||||
var node yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &node)
|
||||
|
||||
// build out the low-level model
|
||||
var lowSchema lowbase.SchemaProxy
|
||||
_ = low.BuildModel(&node, &lowSchema)
|
||||
_ = lowSchema.Build(node.Content[0], nil)
|
||||
|
||||
// build the high level schema proxy
|
||||
highSchema := NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{
|
||||
Value: &lowSchema,
|
||||
})
|
||||
|
||||
// print out the description of 'aProperty'
|
||||
fmt.Print(highSchema.Schema().Properties["aProperty"].Schema().Description)
|
||||
// Output: this is an integer property
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
low "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
)
|
||||
|
||||
// Tag represents a high-level Tag instance that is backed by a low-level one.
|
||||
// - v2: https://swagger.io/specification/v2/#tagObject
|
||||
// - v3: https://swagger.io/specification/#tag-object
|
||||
type Tag struct {
|
||||
Name string
|
||||
Description string
|
||||
@@ -16,6 +19,7 @@ type Tag struct {
|
||||
low *low.Tag
|
||||
}
|
||||
|
||||
// NewTag creates a new high-level Tag instance that is backed by a low-level one.
|
||||
func NewTag(tag *low.Tag) *Tag {
|
||||
t := new(Tag)
|
||||
t.low = tag
|
||||
@@ -32,10 +36,12 @@ func NewTag(tag *low.Tag) *Tag {
|
||||
return t
|
||||
}
|
||||
|
||||
// GoLow returns the low-level Tag instance used to create the high-level one.
|
||||
func (t *Tag) GoLow() *low.Tag {
|
||||
return t.low
|
||||
}
|
||||
|
||||
// Experimental mutation API.
|
||||
//func (t *Tag) SetName(value string) {
|
||||
// t.GoLow().Name.ValueNode.Value = value
|
||||
//}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
|
||||
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -38,3 +39,31 @@ x-hack: code`
|
||||
assert.Equal(t, 5, wentLow.FindExtension("x-hack").ValueNode.Line)
|
||||
|
||||
}
|
||||
|
||||
func ExampleNewTag() {
|
||||
|
||||
// create an example schema object
|
||||
// this can be either JSON or YAML.
|
||||
yml := `
|
||||
name: Purchases
|
||||
description: All kinds of purchase related operations
|
||||
externalDocs:
|
||||
url: https://pb33f.io/purchases
|
||||
x-hack: code`
|
||||
|
||||
// unmarshal raw bytes
|
||||
var node yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &node)
|
||||
|
||||
// build out the low-level model
|
||||
var lowTag lowbase.Tag
|
||||
_ = lowmodel.BuildModel(&node, &lowTag)
|
||||
_ = lowTag.Build(node.Content[0], nil)
|
||||
|
||||
// build the high level tag
|
||||
highTag := NewTag(&lowTag)
|
||||
|
||||
// print out the tag name
|
||||
fmt.Print(highTag.Name)
|
||||
// Output: Purchases
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
low "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
)
|
||||
|
||||
// XML represents a high-level representation of an XML object defined by all versions of OpenAPI and backed by
|
||||
// low-level XML object.
|
||||
// v2 - https://swagger.io/specification/v2/#xmlObject
|
||||
// v3 - https://swagger.io/specification/#xml-object
|
||||
type XML struct {
|
||||
Name string
|
||||
Namespace string
|
||||
@@ -18,6 +22,7 @@ type XML struct {
|
||||
low *low.XML
|
||||
}
|
||||
|
||||
// NewXML creates a new high-level XML instance from a low-level one.
|
||||
func NewXML(xml *low.XML) *XML {
|
||||
x := new(XML)
|
||||
x.low = xml
|
||||
@@ -30,6 +35,7 @@ func NewXML(xml *low.XML) *XML {
|
||||
return x
|
||||
}
|
||||
|
||||
// GoLow returns the low level XML reference used to create the high level one.
|
||||
func (x *XML) GoLow() *low.XML {
|
||||
return x.low
|
||||
}
|
||||
|
||||
36
datamodel/high/base/xml_test.go
Normal file
36
datamodel/high/base/xml_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
|
||||
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func ExampleNewXML() {
|
||||
|
||||
// create an example schema object
|
||||
// this can be either JSON or YAML.
|
||||
yml := `
|
||||
namespace: https://pb33f.io/schema
|
||||
prefix: sample`
|
||||
|
||||
// unmarshal raw bytes
|
||||
var node yaml.Node
|
||||
_ = yaml.Unmarshal([]byte(yml), &node)
|
||||
|
||||
// build out the low-level model
|
||||
var lowXML lowbase.XML
|
||||
_ = lowmodel.BuildModel(&node, &lowXML)
|
||||
_ = lowXML.Build(node.Content[0], nil)
|
||||
|
||||
// build the high level tag
|
||||
highXML := NewXML(&lowXML)
|
||||
|
||||
// print out the XML namespace
|
||||
fmt.Print(highXML.Namespace)
|
||||
// Output: https://pb33f.io/schema
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
|
||||
var xml XML
|
||||
_ = low.BuildModel(xmlNode, &xml)
|
||||
// extract extensions if set.
|
||||
_ = xml.Build(xmlNode) // returns no errors, can't check for one.
|
||||
_ = xml.Build(xmlNode, idx) // returns no errors, can't check for one.
|
||||
s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package base
|
||||
|
||||
import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
"github.com/pb33f/libopenapi/index"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -14,7 +15,7 @@ type XML struct {
|
||||
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
||||
}
|
||||
|
||||
func (x *XML) Build(root *yaml.Node) error {
|
||||
func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
|
||||
x.Extensions = low.ExtractExtensions(root)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user