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

View File

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

View File

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

View File

@@ -4,37 +4,66 @@
package base
import (
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
"fmt"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)
func TestNewTag(t *testing.T) {
var cNode yaml.Node
var cNode yaml.Node
yml := `name: chicken
yml := `name: chicken
description: nuggets
externalDocs:
url: https://pb33f.io
x-hack: code`
_ = yaml.Unmarshal([]byte(yml), &cNode)
_ = yaml.Unmarshal([]byte(yml), &cNode)
var lowTag lowbase.Tag
_ = lowmodel.BuildModel(&cNode, &lowTag)
_ = lowTag.Build(cNode.Content[0], nil)
var lowTag lowbase.Tag
_ = lowmodel.BuildModel(&cNode, &lowTag)
_ = lowTag.Build(cNode.Content[0], nil)
highTag := NewTag(&lowTag)
highTag := NewTag(&lowTag)
assert.Equal(t, "chicken", highTag.Name)
assert.Equal(t, "nuggets", highTag.Description)
assert.Equal(t, "https://pb33f.io", highTag.ExternalDocs.URL)
assert.Equal(t, "code", highTag.Extensions["x-hack"])
assert.Equal(t, "chicken", highTag.Name)
assert.Equal(t, "nuggets", highTag.Description)
assert.Equal(t, "https://pb33f.io", highTag.ExternalDocs.URL)
assert.Equal(t, "code", highTag.Extensions["x-hack"])
wentLow := highTag.GoLow()
assert.Equal(t, 5, wentLow.FindExtension("x-hack").ValueNode.Line)
wentLow := highTag.GoLow()
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
import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/datamodel/high"
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
Prefix string
Attribute bool
Wrapped bool
Extensions map[string]any
low *low.XML
Name string
Namespace string
Prefix string
Attribute bool
Wrapped bool
Extensions map[string]any
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
x.Name = xml.Name.Value
x.Namespace = xml.Namespace.Value
x.Prefix = xml.Namespace.Value
x.Attribute = xml.Attribute.Value
x.Wrapped = xml.Wrapped.Value
x.Extensions = high.ExtractExtensions(xml.Extensions)
return x
x := new(XML)
x.low = xml
x.Name = xml.Name.Value
x.Namespace = xml.Namespace.Value
x.Prefix = xml.Namespace.Value
x.Attribute = xml.Attribute.Value
x.Wrapped = xml.Wrapped.Value
x.Extensions = high.ExtractExtensions(xml.Extensions)
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
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
import (
"github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3"
)
type XML struct {
Name low.NodeReference[string]
Namespace low.NodeReference[string]
Prefix low.NodeReference[string]
Attribute low.NodeReference[bool]
Wrapped low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
Name low.NodeReference[string]
Namespace low.NodeReference[string]
Prefix low.NodeReference[string]
Attribute low.NodeReference[bool]
Wrapped low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
func (x *XML) Build(root *yaml.Node) error {
x.Extensions = low.ExtractExtensions(root)
return nil
func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
x.Extensions = low.ExtractExtensions(root)
return nil
}