Enums in Schema are now interface{} types in datamodel #1

Addressing issues #1, Enums can be anything and should not be consideded plain strings.
This commit is contained in:
Dave Shanley
2022-10-27 06:51:37 -04:00
parent 626fe42ded
commit 54d924d46d
4 changed files with 269 additions and 248 deletions

View File

@@ -4,10 +4,11 @@
package base
import (
"github.com/pb33f/libopenapi/datamodel/high"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"sync"
"fmt"
"github.com/pb33f/libopenapi/datamodel/high"
lowmodel "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"sync"
)
// Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1
@@ -21,273 +22,273 @@ import (
// - 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
// 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 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 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 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 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
// 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
// Schemas are resolved on demand using a SchemaProxy
AllOf []*SchemaProxy
// Polymorphic Schemas are only available in version 3+
OneOf []*SchemaProxy
AnyOf []*SchemaProxy
Discriminator *Discriminator
// 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
// 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
// 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 []any
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
}
if !schema.MaxLength.IsEmpty() {
s.MaxLength = &schema.MaxLength.Value
}
if !schema.MinLength.IsEmpty() {
s.MinLength = &schema.MinLength.Value
}
if !schema.MaxItems.IsEmpty() {
s.MaxItems = &schema.MaxItems.Value
}
if !schema.MinItems.IsEmpty() {
s.MinItems = &schema.MinItems.Value
}
if !schema.MaxProperties.IsEmpty() {
s.MaxProperties = &schema.MaxProperties.Value
}
if !schema.MinProperties.IsEmpty() {
s.MinProperties = &schema.MinProperties.Value
}
s.Pattern = schema.Pattern.Value
s.Format = schema.Format.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
}
if !schema.MaxLength.IsEmpty() {
s.MaxLength = &schema.MaxLength.Value
}
if !schema.MinLength.IsEmpty() {
s.MinLength = &schema.MinLength.Value
}
if !schema.MaxItems.IsEmpty() {
s.MaxItems = &schema.MaxItems.Value
}
if !schema.MinItems.IsEmpty() {
s.MinItems = &schema.MinItems.Value
}
if !schema.MaxProperties.IsEmpty() {
s.MaxProperties = &schema.MaxProperties.Value
}
if !schema.MinProperties.IsEmpty() {
s.MinProperties = &schema.MinProperties.Value
}
s.Pattern = schema.Pattern.Value
s.Format = schema.Format.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
if !schema.Nullable.IsEmpty() {
s.Nullable = &schema.Nullable.Value
}
if !schema.ReadOnly.IsEmpty() {
s.ReadOnly = &schema.ReadOnly.Value
}
if !schema.WriteOnly.IsEmpty() {
s.WriteOnly = &schema.WriteOnly.Value
}
if !schema.Deprecated.IsEmpty() {
s.Deprecated = &schema.Deprecated.Value
}
s.Example = schema.Example.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
if !schema.Nullable.IsEmpty() {
s.Nullable = &schema.Nullable.Value
}
if !schema.ReadOnly.IsEmpty() {
s.ReadOnly = &schema.ReadOnly.Value
}
if !schema.WriteOnly.IsEmpty() {
s.WriteOnly = &schema.WriteOnly.Value
}
if !schema.Deprecated.IsEmpty() {
s.Deprecated = &schema.Deprecated.Value
}
s.Example = schema.Example.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 []any
for i := range schema.Enum.Value {
enum = append(enum, fmt.Sprint(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

@@ -86,7 +86,7 @@ type Schema struct {
MaxProperties low.NodeReference[int64]
MinProperties low.NodeReference[int64]
Required low.NodeReference[[]low.ValueReference[string]]
Enum low.NodeReference[[]low.ValueReference[string]]
Enum low.NodeReference[[]low.ValueReference[any]]
Not low.NodeReference[[]low.ValueReference[*SchemaProxy]]
Items low.NodeReference[[]low.ValueReference[*SchemaProxy]]
Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
@@ -143,7 +143,7 @@ func (s *Schema) Hash() [32]byte {
d = append(d, s.Required.Value[i].Value)
}
for i := range s.Enum.Value {
d = append(d, s.Enum.Value[i].Value)
d = append(d, fmt.Sprint(s.Enum.Value[i].Value))
}
propertyKeys := make([]string, 0, len(s.Properties.Value))
for i := range s.Properties.Value {

View File

@@ -424,6 +424,26 @@ func SetField(field reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) err
}
}
}
case reflect.TypeOf(NodeReference[[]ValueReference[any]]{}):
if valueNode != nil {
if utils.IsNodeArray(valueNode) {
if field.CanSet() {
var items []ValueReference[any]
for _, sliceItem := range valueNode.Content {
items = append(items, ValueReference[any]{
Value: sliceItem.Value,
ValueNode: sliceItem,
})
}
n := NodeReference[[]ValueReference[any]]{
Value: items,
KeyNode: keyNode,
ValueNode: valueNode,
}
field.Set(reflect.ValueOf(n))
}
}
}
default:
// we want to ignore everything else, each model handles its own complex types.
break

View File

@@ -683,10 +683,10 @@ func checkSchemaPropertyChanges(
j = make(map[string]int)
k = make(map[string]int)
for i := range lSchema.Enum.Value {
j[lSchema.Enum.Value[i].Value] = i
j[fmt.Sprint(lSchema.Enum.Value[i].Value)] = i
}
for i := range rSchema.Enum.Value {
k[rSchema.Enum.Value[i].Value] = i
k[fmt.Sprint(rSchema.Enum.Value[i].Value)] = i
}
for g := range k {
if _, ok := j[g]; !ok {