diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index 6e90f47..2a625ce 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -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, ¬, 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, ¬, 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 } diff --git a/datamodel/low/base/schema.go b/datamodel/low/base/schema.go index 7c0b5e5..c5a5aa0 100644 --- a/datamodel/low/base/schema.go +++ b/datamodel/low/base/schema.go @@ -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 { diff --git a/datamodel/low/model_builder.go b/datamodel/low/model_builder.go index f0d3da0..da5d926 100644 --- a/datamodel/low/model_builder.go +++ b/datamodel/low/model_builder.go @@ -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 diff --git a/what-changed/schema.go b/what-changed/schema.go index 10dd486..a1512aa 100644 --- a/what-changed/schema.go +++ b/what-changed/schema.go @@ -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 {