High-level base documentation is complete.

Examples and every model completed, 1/6th of the way through models.
This commit is contained in:
Dave Shanley
2022-09-15 11:13:54 -04:00
parent b036982212
commit 849074d0bc
9 changed files with 1030 additions and 818 deletions

View File

@@ -4,239 +4,270 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"sync" "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 { type Schema struct {
SchemaTypeRef string
Title string // 3.1 only, used to define a dialect for this schema, label is '$schema'.
MultipleOf int64 SchemaTypeRef string
Maximum int64
ExclusiveMaximumBool bool // In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean.
ExclusiveMaximum int64 ExclusiveMaximumBool bool
Minimum int64
ExclusiveMinimum int64 // In version 3.1, ExclusiveMaximum is an integer.
ExclusiveMinimumBool bool ExclusiveMaximum int64
MaxLength int64
MinLength int64 // In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean.
Pattern string ExclusiveMinimum int64
Format string
MaxItems int64 // In version 3.1, ExclusiveMinimum is an integer.
MinItems int64 ExclusiveMinimumBool bool
UniqueItems int64
MaxProperties int64 // In versions 2 and 3.0, this Type is a single value, so array will only ever have one value
MinProperties int64 // in version 3.1, Type can be multiple values
Required []string Type []string
Enum []string
Type []string // Schemas are resolved on demand using a SchemaProxy
AllOf []*SchemaProxy AllOf []*SchemaProxy
OneOf []*SchemaProxy
AnyOf []*SchemaProxy // Polymorphic Schemas are only available in version 3+
Not []*SchemaProxy OneOf []*SchemaProxy
Items []*SchemaProxy AnyOf []*SchemaProxy
Properties map[string]*SchemaProxy Discriminator *Discriminator
AdditionalProperties any
Description string // in 3.1 examples can be an array (which is recommended)
Default any Examples []any
Nullable bool
Discriminator *Discriminator // Compatible with all versions
ReadOnly bool Not []*SchemaProxy
WriteOnly bool Items []*SchemaProxy
XML *XML Properties map[string]*SchemaProxy
ExternalDocs *ExternalDoc Title string
Example any MultipleOf int64
Examples []any Maximum int64
Deprecated bool Minimum int64
Extensions map[string]any MaxLength int64
low *base.Schema MinLength int64
Pattern string
Format string
MaxItems int64
MinItems int64
UniqueItems int64
MaxProperties int64
MinProperties int64
Required []string
Enum []string
AdditionalProperties any
Description string
Default any
Nullable bool
ReadOnly bool
WriteOnly bool
XML *XML
ExternalDocs *ExternalDoc
Example 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 { func NewSchema(schema *base.Schema) *Schema {
s := new(Schema) s := new(Schema)
s.low = schema s.low = schema
s.Title = schema.Title.Value s.Title = schema.Title.Value
s.MultipleOf = schema.MultipleOf.Value s.MultipleOf = schema.MultipleOf.Value
s.Maximum = schema.Maximum.Value s.Maximum = schema.Maximum.Value
s.Minimum = schema.Minimum.Value s.Minimum = schema.Minimum.Value
// if we're dealing with a 3.0 spec using a bool // if we're dealing with a 3.0 spec using a bool
if !schema.ExclusiveMaximum.IsEmpty() && schema.ExclusiveMaximum.Value.IsA() { if !schema.ExclusiveMaximum.IsEmpty() && schema.ExclusiveMaximum.Value.IsA() {
s.ExclusiveMaximumBool = schema.ExclusiveMaximum.Value.A s.ExclusiveMaximumBool = schema.ExclusiveMaximum.Value.A
} }
// if we're dealing with a 3.1 spec using an int // if we're dealing with a 3.1 spec using an int
if !schema.ExclusiveMaximum.IsEmpty() && schema.ExclusiveMaximum.Value.IsB() { if !schema.ExclusiveMaximum.IsEmpty() && schema.ExclusiveMaximum.Value.IsB() {
s.ExclusiveMaximum = schema.ExclusiveMaximum.Value.B s.ExclusiveMaximum = schema.ExclusiveMaximum.Value.B
} }
// if we're dealing with a 3.0 spec using a bool // if we're dealing with a 3.0 spec using a bool
if !schema.ExclusiveMinimum.IsEmpty() && schema.ExclusiveMinimum.Value.IsA() { if !schema.ExclusiveMinimum.IsEmpty() && schema.ExclusiveMinimum.Value.IsA() {
s.ExclusiveMinimumBool = schema.ExclusiveMinimum.Value.A s.ExclusiveMinimumBool = schema.ExclusiveMinimum.Value.A
} }
// if we're dealing with a 3.1 spec, using an int // if we're dealing with a 3.1 spec, using an int
if !schema.ExclusiveMinimum.IsEmpty() && schema.ExclusiveMinimum.Value.IsB() { if !schema.ExclusiveMinimum.IsEmpty() && schema.ExclusiveMinimum.Value.IsB() {
s.ExclusiveMinimum = schema.ExclusiveMinimum.Value.B s.ExclusiveMinimum = schema.ExclusiveMinimum.Value.B
} }
s.MaxLength = schema.MaxLength.Value s.MaxLength = schema.MaxLength.Value
s.MinLength = schema.MinLength.Value s.MinLength = schema.MinLength.Value
s.Pattern = schema.Pattern.Value s.Pattern = schema.Pattern.Value
s.Format = schema.Format.Value s.Format = schema.Format.Value
s.MaxItems = schema.MaxItems.Value s.MaxItems = schema.MaxItems.Value
s.MinItems = schema.MinItems.Value s.MinItems = schema.MinItems.Value
s.MaxProperties = schema.MaxProperties.Value s.MaxProperties = schema.MaxProperties.Value
s.MinProperties = schema.MinProperties.Value s.MinProperties = schema.MinProperties.Value
// 3.0 spec is a single value // 3.0 spec is a single value
if !schema.Type.IsEmpty() && schema.Type.Value.IsA() { if !schema.Type.IsEmpty() && schema.Type.Value.IsA() {
s.Type = []string{schema.Type.Value.A} s.Type = []string{schema.Type.Value.A}
} }
// 3.1 spec may have multiple values // 3.1 spec may have multiple values
if !schema.Type.IsEmpty() && schema.Type.Value.IsB() { if !schema.Type.IsEmpty() && schema.Type.Value.IsB() {
for i := range schema.Type.Value.B { for i := range schema.Type.Value.B {
s.Type = append(s.Type, schema.Type.Value.B[i].Value) s.Type = append(s.Type, schema.Type.Value.B[i].Value)
} }
} }
s.AdditionalProperties = schema.AdditionalProperties.Value s.AdditionalProperties = schema.AdditionalProperties.Value
s.Description = schema.Description.Value s.Description = schema.Description.Value
s.Default = schema.Default.Value s.Default = schema.Default.Value
s.Nullable = schema.Nullable.Value s.Nullable = schema.Nullable.Value
s.ReadOnly = schema.ReadOnly.Value s.ReadOnly = schema.ReadOnly.Value
s.WriteOnly = schema.WriteOnly.Value s.WriteOnly = schema.WriteOnly.Value
s.Example = schema.Example.Value s.Example = schema.Example.Value
s.Deprecated = schema.Deprecated.Value s.Deprecated = schema.Deprecated.Value
s.Extensions = high.ExtractExtensions(schema.Extensions) s.Extensions = high.ExtractExtensions(schema.Extensions)
if !schema.Discriminator.IsEmpty() { if !schema.Discriminator.IsEmpty() {
s.Discriminator = NewDiscriminator(schema.Discriminator.Value) s.Discriminator = NewDiscriminator(schema.Discriminator.Value)
} }
if !schema.XML.IsEmpty() { if !schema.XML.IsEmpty() {
s.XML = NewXML(schema.XML.Value) s.XML = NewXML(schema.XML.Value)
} }
if !schema.ExternalDocs.IsEmpty() { if !schema.ExternalDocs.IsEmpty() {
s.ExternalDocs = NewExternalDoc(schema.ExternalDocs.Value) s.ExternalDocs = NewExternalDoc(schema.ExternalDocs.Value)
} }
var req []string var req []string
for i := range schema.Required.Value { for i := range schema.Required.Value {
req = append(req, schema.Required.Value[i].Value) req = append(req, schema.Required.Value[i].Value)
} }
s.Required = req s.Required = req
var enum []string var enum []string
for i := range schema.Enum.Value { for i := range schema.Enum.Value {
enum = append(enum, schema.Enum.Value[i].Value) enum = append(enum, schema.Enum.Value[i].Value)
} }
s.Enum = enum s.Enum = enum
// async work. // async work.
// any polymorphic properties need to be handled in their own threads // any polymorphic properties need to be handled in their own threads
// any properties each need to be processed in their own thread. // any properties each need to be processed in their own thread.
// we go as fast as we can. // we go as fast as we can.
polyCompletedChan := make(chan bool) polyCompletedChan := make(chan bool)
propsChan := make(chan bool) propsChan := make(chan bool)
errChan := make(chan error) errChan := make(chan error)
// schema async // schema async
buildOutSchema := func(schemas []lowmodel.ValueReference[*base.SchemaProxy], items *[]*SchemaProxy, buildOutSchema := func(schemas []lowmodel.ValueReference[*base.SchemaProxy], items *[]*SchemaProxy,
doneChan chan bool, e chan error) { doneChan chan bool, e chan error) {
bChan := make(chan *SchemaProxy) bChan := make(chan *SchemaProxy)
// for every item, build schema async // for every item, build schema async
buildSchemaChild := func(sch lowmodel.ValueReference[*base.SchemaProxy], bChan chan *SchemaProxy) { buildSchemaChild := func(sch lowmodel.ValueReference[*base.SchemaProxy], bChan chan *SchemaProxy) {
p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: sch.ValueNode, ValueNode: sch.ValueNode,
Value: sch.Value, Value: sch.Value,
}} }}
bChan <- p bChan <- p
} }
totalSchemas := len(schemas) totalSchemas := len(schemas)
for v := range schemas { for v := range schemas {
go buildSchemaChild(schemas[v], bChan) go buildSchemaChild(schemas[v], bChan)
} }
j := 0 j := 0
for j < totalSchemas { for j < totalSchemas {
select { select {
case t := <-bChan: case t := <-bChan:
j++ j++
*items = append(*items, t) *items = append(*items, t)
} }
} }
doneChan <- true doneChan <- true
} }
// props async // props async
plock := sync.RWMutex{} plock := sync.RWMutex{}
var buildProps = func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy], c chan bool, var buildProps = func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy], c chan bool,
props map[string]*SchemaProxy) { props map[string]*SchemaProxy) {
defer plock.Unlock() defer plock.Unlock()
plock.Lock() plock.Lock()
props[k.Value] = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ props[k.Value] = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
Value: v.Value, Value: v.Value,
KeyNode: k.KeyNode, KeyNode: k.KeyNode,
ValueNode: v.ValueNode, ValueNode: v.ValueNode,
}, },
} }
s.Properties = props s.Properties = props
c <- true c <- true
} }
props := make(map[string]*SchemaProxy) props := make(map[string]*SchemaProxy)
for k, v := range schema.Properties.Value { for k, v := range schema.Properties.Value {
go buildProps(k, v, propsChan, props) go buildProps(k, v, propsChan, props)
} }
var allOf []*SchemaProxy var allOf []*SchemaProxy
var oneOf []*SchemaProxy var oneOf []*SchemaProxy
var anyOf []*SchemaProxy var anyOf []*SchemaProxy
var not []*SchemaProxy var not []*SchemaProxy
var items []*SchemaProxy var items []*SchemaProxy
if !schema.AllOf.IsEmpty() { if !schema.AllOf.IsEmpty() {
go buildOutSchema(schema.AllOf.Value, &allOf, polyCompletedChan, errChan) go buildOutSchema(schema.AllOf.Value, &allOf, polyCompletedChan, errChan)
} }
if !schema.AnyOf.IsEmpty() { if !schema.AnyOf.IsEmpty() {
go buildOutSchema(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan) go buildOutSchema(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan)
} }
if !schema.OneOf.IsEmpty() { if !schema.OneOf.IsEmpty() {
go buildOutSchema(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan) go buildOutSchema(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan)
} }
if !schema.Not.IsEmpty() { if !schema.Not.IsEmpty() {
go buildOutSchema(schema.Not.Value, &not, polyCompletedChan, errChan) go buildOutSchema(schema.Not.Value, &not, polyCompletedChan, errChan)
} }
if !schema.Items.IsEmpty() { if !schema.Items.IsEmpty() {
go buildOutSchema(schema.Items.Value, &items, polyCompletedChan, errChan) go buildOutSchema(schema.Items.Value, &items, polyCompletedChan, errChan)
} }
completeChildren := 0 completeChildren := 0
completedProps := 0 completedProps := 0
totalProps := len(schema.Properties.Value) totalProps := len(schema.Properties.Value)
totalChildren := len(schema.AllOf.Value) + len(schema.OneOf.Value) + len(schema.AnyOf.Value) + len(schema.Items.Value) + len(schema.Not.Value) totalChildren := len(schema.AllOf.Value) + len(schema.OneOf.Value) + len(schema.AnyOf.Value) + len(schema.Items.Value) + len(schema.Not.Value)
if totalProps+totalChildren > 0 { if totalProps+totalChildren > 0 {
allDone: allDone:
for true { for true {
select { select {
case <-polyCompletedChan: case <-polyCompletedChan:
completeChildren++ completeChildren++
if totalProps == completedProps && totalChildren == completeChildren { if totalProps == completedProps && totalChildren == completeChildren {
break allDone break allDone
} }
case <-propsChan: case <-propsChan:
completedProps++ completedProps++
if totalProps == completedProps && totalChildren == completeChildren { if totalProps == completedProps && totalChildren == completeChildren {
break allDone break allDone
} }
} }
} }
} }
s.OneOf = oneOf s.OneOf = oneOf
s.AnyOf = anyOf s.AnyOf = anyOf
s.AllOf = allOf s.AllOf = allOf
s.Items = items s.Items = items
s.Not = not s.Not = not
return s 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 { func (s *Schema) GoLow() *base.Schema {
return s.low return s.low
} }

View File

@@ -8,15 +8,52 @@ import (
"github.com/pb33f/libopenapi/datamodel/low/base" "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 { type SchemaProxy struct {
schema *low.NodeReference[*base.SchemaProxy] schema *low.NodeReference[*base.SchemaProxy]
buildError error buildError error
} }
// NewSchemaProxy creates a new high-level SchemaProxy from a low-level one.
func NewSchemaProxy(schema *low.NodeReference[*base.SchemaProxy]) *SchemaProxy { func NewSchemaProxy(schema *low.NodeReference[*base.SchemaProxy]) *SchemaProxy {
return &SchemaProxy{schema: schema} 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 { func (sp *SchemaProxy) Schema() *Schema {
s := sp.schema.Value.Schema() s := sp.schema.Value.Schema()
if s == nil { if s == nil {
@@ -26,6 +63,7 @@ func (sp *SchemaProxy) Schema() *Schema {
return NewSchema(s) return NewSchema(s)
} }
// GetBuildError returns any error that was thrown when calling Schema()
func (sp *SchemaProxy) GetBuildError() error { func (sp *SchemaProxy) GetBuildError() error {
return sp.buildError return sp.buildError
} }

View File

@@ -4,18 +4,19 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "fmt"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert" "github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3" "github.com/stretchr/testify/assert"
"testing" "gopkg.in/yaml.v3"
"testing"
) )
func TestNewSchemaProxy(t *testing.T) { func TestNewSchemaProxy(t *testing.T) {
// check proxy // check proxy
yml := `components: yml := `components:
schemas: schemas:
rice: rice:
type: string type: string
@@ -28,34 +29,34 @@ func TestNewSchemaProxy(t *testing.T) {
rice: rice:
$ref: '#/components/schemas/rice'` $ref: '#/components/schemas/rice'`
var idxNode, compNode yaml.Node var idxNode, compNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode) mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
yml = `properties: yml = `properties:
rice: rice:
$ref: '#/components/schemas/I-do-not-exist'` $ref: '#/components/schemas/I-do-not-exist'`
_ = yaml.Unmarshal([]byte(yml), &compNode) _ = yaml.Unmarshal([]byte(yml), &compNode)
sp := new(lowbase.SchemaProxy) sp := new(lowbase.SchemaProxy)
err := sp.Build(compNode.Content[0], idx) err := sp.Build(compNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{ lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp, Value: sp,
ValueNode: idxNode.Content[0], ValueNode: idxNode.Content[0],
} }
sch1 := SchemaProxy{schema: &lowproxy} sch1 := SchemaProxy{schema: &lowproxy}
assert.Nil(t, sch1.Schema()) assert.Nil(t, sch1.Schema())
assert.Error(t, sch1.GetBuildError()) assert.Error(t, sch1.GetBuildError())
} }
func TestNewSchemaProxy_WithObject(t *testing.T) { func TestNewSchemaProxy_WithObject(t *testing.T) {
testSpec := `type: object testSpec := `type: object
description: something object description: something object
discriminator: discriminator:
propertyName: athing propertyName: athing
@@ -164,32 +165,32 @@ externalDocs:
enum: [fish, cake] enum: [fish, cake]
required: [cake, fish]` required: [cake, fish]`
var compNode yaml.Node var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode) _ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy) sp := new(lowbase.SchemaProxy)
err := sp.Build(compNode.Content[0], nil) err := sp.Build(compNode.Content[0], nil)
assert.NoError(t, err) assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{ lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp, Value: sp,
ValueNode: compNode.Content[0], ValueNode: compNode.Content[0],
} }
schemaProxy := NewSchemaProxy(&lowproxy) schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema() compiled := schemaProxy.Schema()
assert.NotNil(t, compiled) assert.NotNil(t, compiled)
assert.Nil(t, schemaProxy.GetBuildError()) assert.Nil(t, schemaProxy.GetBuildError())
wentLow := compiled.GoLow() wentLow := compiled.GoLow()
assert.Equal(t, 102, wentLow.AdditionalProperties.ValueNode.Line) assert.Equal(t, 102, wentLow.AdditionalProperties.ValueNode.Line)
} }
func TestNewSchemaProxy_WithObject_FinishPoly(t *testing.T) { func TestNewSchemaProxy_WithObject_FinishPoly(t *testing.T) {
testSpec := `type: object testSpec := `type: object
description: something object description: something object
discriminator: discriminator:
propertyName: athing propertyName: athing
@@ -294,35 +295,99 @@ externalDocs:
enum: [fish, cake] enum: [fish, cake]
required: [cake, fish]` required: [cake, fish]`
var compNode yaml.Node var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode) _ = yaml.Unmarshal([]byte(testSpec), &compNode)
sp := new(lowbase.SchemaProxy) sp := new(lowbase.SchemaProxy)
err := sp.Build(compNode.Content[0], nil) err := sp.Build(compNode.Content[0], nil)
assert.NoError(t, err) assert.NoError(t, err)
lowproxy := low.NodeReference[*lowbase.SchemaProxy]{ lowproxy := low.NodeReference[*lowbase.SchemaProxy]{
Value: sp, Value: sp,
ValueNode: compNode.Content[0], ValueNode: compNode.Content[0],
} }
schemaProxy := NewSchemaProxy(&lowproxy) schemaProxy := NewSchemaProxy(&lowproxy)
compiled := schemaProxy.Schema() compiled := schemaProxy.Schema()
assert.NotNil(t, compiled) assert.NotNil(t, compiled)
assert.Nil(t, schemaProxy.GetBuildError()) assert.Nil(t, schemaProxy.GetBuildError())
assert.True(t, compiled.ExclusiveMaximumBool) assert.True(t, compiled.ExclusiveMaximumBool)
assert.False(t, compiled.ExclusiveMinimumBool) assert.False(t, compiled.ExclusiveMinimumBool)
assert.Equal(t, int64(123), compiled.Properties["somethingB"].Schema().ExclusiveMinimum) assert.Equal(t, int64(123), compiled.Properties["somethingB"].Schema().ExclusiveMinimum)
assert.Equal(t, int64(334), compiled.Properties["somethingB"].Schema().ExclusiveMaximum) assert.Equal(t, int64(334), compiled.Properties["somethingB"].Schema().ExclusiveMaximum)
assert.Len(t, compiled.Properties["somethingB"].Schema().Properties["somethingBProp"].Schema().Type, 2) assert.Len(t, compiled.Properties["somethingB"].Schema().Properties["somethingBProp"].Schema().Type, 2)
wentLow := compiled.GoLow() wentLow := compiled.GoLow()
assert.Equal(t, 96, wentLow.AdditionalProperties.ValueNode.Line) assert.Equal(t, 96, wentLow.AdditionalProperties.ValueNode.Line)
assert.Equal(t, 100, wentLow.XML.ValueNode.Line) assert.Equal(t, 100, wentLow.XML.ValueNode.Line)
wentLower := compiled.XML.GoLow() wentLower := compiled.XML.GoLow()
assert.Equal(t, 100, wentLower.Name.ValueNode.Line) 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
} }

View File

@@ -4,38 +4,44 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" 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 { type Tag struct {
Name string Name string
Description string Description string
ExternalDocs *ExternalDoc ExternalDocs *ExternalDoc
Extensions map[string]any Extensions map[string]any
low *low.Tag 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 { func NewTag(tag *low.Tag) *Tag {
t := new(Tag) t := new(Tag)
t.low = tag t.low = tag
if !tag.Name.IsEmpty() { if !tag.Name.IsEmpty() {
t.Name = tag.Name.Value t.Name = tag.Name.Value
} }
if !tag.Description.IsEmpty() { if !tag.Description.IsEmpty() {
t.Description = tag.Description.Value t.Description = tag.Description.Value
} }
if !tag.ExternalDocs.IsEmpty() { if !tag.ExternalDocs.IsEmpty() {
t.ExternalDocs = NewExternalDoc(tag.ExternalDocs.Value) t.ExternalDocs = NewExternalDoc(tag.ExternalDocs.Value)
} }
t.Extensions = high.ExtractExtensions(tag.Extensions) t.Extensions = high.ExtractExtensions(tag.Extensions)
return t return t
} }
// GoLow returns the low-level Tag instance used to create the high-level one.
func (t *Tag) GoLow() *low.Tag { func (t *Tag) GoLow() *low.Tag {
return t.low return t.low
} }
// Experimental mutation API.
//func (t *Tag) SetName(value string) { //func (t *Tag) SetName(value string) {
// t.GoLow().Name.ValueNode.Value = value // t.GoLow().Name.ValueNode.Value = value
//} //}

View File

@@ -4,37 +4,66 @@
package base package base
import ( import (
lowmodel "github.com/pb33f/libopenapi/datamodel/low" "fmt"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
"github.com/stretchr/testify/assert" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"gopkg.in/yaml.v3" "github.com/stretchr/testify/assert"
"testing" "gopkg.in/yaml.v3"
"testing"
) )
func TestNewTag(t *testing.T) { func TestNewTag(t *testing.T) {
var cNode yaml.Node var cNode yaml.Node
yml := `name: chicken yml := `name: chicken
description: nuggets description: nuggets
externalDocs: externalDocs:
url: https://pb33f.io url: https://pb33f.io
x-hack: code` x-hack: code`
_ = yaml.Unmarshal([]byte(yml), &cNode) _ = yaml.Unmarshal([]byte(yml), &cNode)
var lowTag lowbase.Tag var lowTag lowbase.Tag
_ = lowmodel.BuildModel(&cNode, &lowTag) _ = lowmodel.BuildModel(&cNode, &lowTag)
_ = lowTag.Build(cNode.Content[0], nil) _ = lowTag.Build(cNode.Content[0], nil)
highTag := NewTag(&lowTag) highTag := NewTag(&lowTag)
assert.Equal(t, "chicken", highTag.Name) assert.Equal(t, "chicken", highTag.Name)
assert.Equal(t, "nuggets", highTag.Description) assert.Equal(t, "nuggets", highTag.Description)
assert.Equal(t, "https://pb33f.io", highTag.ExternalDocs.URL) assert.Equal(t, "https://pb33f.io", highTag.ExternalDocs.URL)
assert.Equal(t, "code", highTag.Extensions["x-hack"]) assert.Equal(t, "code", highTag.Extensions["x-hack"])
wentLow := highTag.GoLow() wentLow := highTag.GoLow()
assert.Equal(t, 5, wentLow.FindExtension("x-hack").ValueNode.Line) 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
}

View File

@@ -4,32 +4,38 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" 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 { type XML struct {
Name string Name string
Namespace string Namespace string
Prefix string Prefix string
Attribute bool Attribute bool
Wrapped bool Wrapped bool
Extensions map[string]any Extensions map[string]any
low *low.XML low *low.XML
} }
// NewXML creates a new high-level XML instance from a low-level one.
func NewXML(xml *low.XML) *XML { func NewXML(xml *low.XML) *XML {
x := new(XML) x := new(XML)
x.low = xml x.low = xml
x.Name = xml.Name.Value x.Name = xml.Name.Value
x.Namespace = xml.Namespace.Value x.Namespace = xml.Namespace.Value
x.Prefix = xml.Namespace.Value x.Prefix = xml.Namespace.Value
x.Attribute = xml.Attribute.Value x.Attribute = xml.Attribute.Value
x.Wrapped = xml.Wrapped.Value x.Wrapped = xml.Wrapped.Value
x.Extensions = high.ExtractExtensions(xml.Extensions) x.Extensions = high.ExtractExtensions(xml.Extensions)
return x return x
} }
// GoLow returns the low level XML reference used to create the high level one.
func (x *XML) GoLow() *low.XML { func (x *XML) GoLow() *low.XML {
return x.low return x.low
} }

View 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
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,21 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3" "github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3"
) )
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]
} }
func (x *XML) Build(root *yaml.Node) 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
} }