Working on model changes to resolve #38 and #28 **breaking change**

Also adding in other properties to schema that are missing. Test coverage still needs improving and this is a breaking change to low and high models.
This commit is contained in:
Dave Shanley
2022-12-08 16:51:36 -05:00
parent d51d2fcd27
commit b3f0a0b1ae
9 changed files with 1349 additions and 286 deletions

View File

@@ -11,6 +11,30 @@ import (
"sync" "sync"
) )
// DynamicValue is used to hold multiple possible values for a schema property. There are two values, a left
// value (A) and a right value (B). The left value (A) is a 3.0 schema property value, the right value (B) is a 3.1
// schema value.
//
// OpenAPI 3.1 treats a Schema as a real JSON schema, which means some properties become incompatible, or others
// now support more than one primitive type or structure.
// The N value is a bit to make it each to know which value (A or B) is used, this prevents having to
// if/else on the value to determine which one is set.
type DynamicValue[A any, B any] struct {
N int // 0 == A, 1 == B
A A
B B
}
// IsA will return true if the 'A' or left value is set. (OpenAPI 3)
func (s DynamicValue[A, B]) IsA() bool {
return s.N == 0
}
// IsB will return true if the 'B' or right value is set (OpenAPI 3.1)
func (s DynamicValue[A, B]) IsB() bool {
return s.N == 1
}
// Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1 // 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 // Until 3.1 OpenAPI had a strange relationship with JSON Schema. It's been a super-set/sub-set
@@ -26,16 +50,12 @@ type Schema struct {
SchemaTypeRef string SchemaTypeRef string
// In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean. // In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean.
ExclusiveMaximumBool *bool
// In version 3.1, ExclusiveMaximum is an integer. // In version 3.1, ExclusiveMaximum is an integer.
ExclusiveMaximum *int64 ExclusiveMaximum *DynamicValue[bool, int64]
// In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean. // In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean.
ExclusiveMinimum *int64
// In version 3.1, ExclusiveMinimum is an integer. // In version 3.1, ExclusiveMinimum is an integer.
ExclusiveMinimumBool *bool ExclusiveMinimum *DynamicValue[bool, int64]
// In versions 2 and 3.0, this Type is a single value, so array will only ever have one value // 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 // in version 3.1, Type can be multiple values
@@ -55,14 +75,24 @@ type Schema struct {
// in 3.1 prefixItems provides tuple validation support. // in 3.1 prefixItems provides tuple validation support.
PrefixItems []*SchemaProxy PrefixItems []*SchemaProxy
// In 3.1 contains is used by arrays to define a single schema // 3.1 Specific properties
Contains *SchemaProxy Contains *SchemaProxy
MinContains *int64 MinContains *int64
MaxContains *int64 MaxContains *int64
If *SchemaProxy
Else *SchemaProxy
Then *SchemaProxy
DependentSchemas map[string]*SchemaProxy
PatternProperties map[string]*SchemaProxy
PropertyNames *SchemaProxy
UnevaluatedItems *SchemaProxy
UnevaluatedProperties *SchemaProxy
// in 3.1 Items can be a Schema or a boolean
Items *DynamicValue[*SchemaProxy, bool]
// Compatible with all versions // Compatible with all versions
Not []*SchemaProxy Not *SchemaProxy
Items *SchemaProxy
Properties map[string]*SchemaProxy Properties map[string]*SchemaProxy
Title string Title string
MultipleOf *int64 MultipleOf *int64
@@ -112,19 +142,27 @@ func NewSchema(schema *base.Schema) *Schema {
} }
// 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.ExclusiveMaximum = &DynamicValue[bool, int64]{
A: 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 = &DynamicValue[bool, int64]{
B: 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.ExclusiveMinimum = &DynamicValue[bool, int64]{
A: 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 = &DynamicValue[bool, int64]{
B: schema.ExclusiveMinimum.Value.B,
}
} }
if !schema.MaxLength.IsEmpty() { if !schema.MaxLength.IsEmpty() {
s.MaxLength = &schema.MaxLength.Value s.MaxLength = &schema.MaxLength.Value
@@ -144,6 +182,7 @@ func NewSchema(schema *base.Schema) *Schema {
if !schema.MinProperties.IsEmpty() { if !schema.MinProperties.IsEmpty() {
s.MinProperties = &schema.MinProperties.Value s.MinProperties = &schema.MinProperties.Value
} }
if !schema.MaxContains.IsEmpty() { if !schema.MaxContains.IsEmpty() {
s.MaxContains = &schema.MaxContains.Value s.MaxContains = &schema.MaxContains.Value
} }
@@ -157,6 +196,43 @@ func NewSchema(schema *base.Schema) *Schema {
}} }}
} }
if !schema.If.IsEmpty() {
s.If = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.If.ValueNode,
Value: schema.If.Value,
}}
}
if !schema.Else.IsEmpty() {
s.Else = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Else.ValueNode,
Value: schema.Else.Value,
}}
}
if !schema.Then.IsEmpty() {
s.Then = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Then.ValueNode,
Value: schema.Then.Value,
}}
}
if !schema.PropertyNames.IsEmpty() {
s.PropertyNames = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.PropertyNames.ValueNode,
Value: schema.PropertyNames.Value,
}}
}
if !schema.UnevaluatedItems.IsEmpty() {
s.UnevaluatedItems = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.UnevaluatedItems.ValueNode,
Value: schema.UnevaluatedItems.Value,
}}
}
if !schema.UnevaluatedProperties.IsEmpty() {
s.UnevaluatedProperties = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.UnevaluatedProperties.ValueNode,
Value: schema.UnevaluatedProperties.Value,
}}
}
s.Pattern = schema.Pattern.Value s.Pattern = schema.Pattern.Value
s.Format = schema.Format.Value s.Format = schema.Format.Value
@@ -215,33 +291,30 @@ func NewSchema(schema *base.Schema) *Schema {
} }
s.Enum = enum s.Enum = enum
// build out
// 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
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 // for every item, build schema async
buildSchemaChild := func(sch lowmodel.ValueReference[*base.SchemaProxy], bChan chan *SchemaProxy) { buildSchema := 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
} }
// schema async
buildOutSchemas := func(schemas []lowmodel.ValueReference[*base.SchemaProxy], items *[]*SchemaProxy,
doneChan chan bool, e chan error) {
bChan := make(chan *SchemaProxy)
totalSchemas := len(schemas) totalSchemas := len(schemas)
for v := range schemas { for v := range schemas {
go buildSchemaChild(schemas[v], bChan) go buildSchema(schemas[v], bChan)
} }
j := 0 j := 0
for j < totalSchemas { for j < totalSchemas {
@@ -257,7 +330,7 @@ func NewSchema(schema *base.Schema) *Schema {
// props async // props async
var plock sync.Mutex var plock sync.Mutex
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, sw int) {
plock.Lock() plock.Lock()
props[k.Value] = &SchemaProxy{ props[k.Value] = &SchemaProxy{
schema: &lowmodel.NodeReference[*base.SchemaProxy]{ schema: &lowmodel.NodeReference[*base.SchemaProxy]{
@@ -267,51 +340,74 @@ func NewSchema(schema *base.Schema) *Schema {
}, },
} }
plock.Unlock() plock.Unlock()
switch sw {
case 0:
s.Properties = props s.Properties = props
case 1:
s.DependentSchemas = props
case 2:
s.PatternProperties = 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, 0)
}
dependents := make(map[string]*SchemaProxy)
for k, v := range schema.DependentSchemas.Value {
go buildProps(k, v, propsChan, dependents, 1)
}
patternProps := make(map[string]*SchemaProxy)
for k, v := range schema.PatternProperties.Value {
go buildProps(k, v, propsChan, patternProps, 2)
} }
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 *DynamicValue[*SchemaProxy, bool]
var prefixItems []*SchemaProxy var prefixItems []*SchemaProxy
children := 0 children := 0
if !schema.AllOf.IsEmpty() { if !schema.AllOf.IsEmpty() {
children++ children++
go buildOutSchema(schema.AllOf.Value, &allOf, polyCompletedChan, errChan) go buildOutSchemas(schema.AllOf.Value, &allOf, polyCompletedChan, errChan)
} }
if !schema.AnyOf.IsEmpty() { if !schema.AnyOf.IsEmpty() {
children++ children++
go buildOutSchema(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan) go buildOutSchemas(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan)
} }
if !schema.OneOf.IsEmpty() { if !schema.OneOf.IsEmpty() {
children++ children++
go buildOutSchema(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan) go buildOutSchemas(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan)
} }
if !schema.Not.IsEmpty() { if !schema.Not.IsEmpty() {
children++ not = NewSchemaProxy(&schema.Not)
go buildOutSchema(schema.Not.Value, &not, polyCompletedChan, errChan)
} }
if !schema.Items.IsEmpty() { if !schema.Items.IsEmpty() {
children++ if schema.Items.Value.IsA() {
go buildOutSchema(schema.Items.Value, &items, polyCompletedChan, errChan) items = &DynamicValue[*SchemaProxy, bool]{A: &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Items.ValueNode,
Value: schema.Items.Value.A,
KeyNode: schema.Items.KeyNode,
}}}
} else {
items = &DynamicValue[*SchemaProxy, bool]{B: schema.Items.Value.B}
}
} }
if !schema.PrefixItems.IsEmpty() { if !schema.PrefixItems.IsEmpty() {
children++ children++
go buildOutSchema(schema.PrefixItems.Value, &prefixItems, polyCompletedChan, errChan) go buildOutSchemas(schema.PrefixItems.Value, &prefixItems, polyCompletedChan, errChan)
} }
completeChildren := 0 completeChildren := 0
completedProps := 0 completedProps := 0
totalProps := len(schema.Properties.Value) totalProps := len(schema.Properties.Value) + len(schema.DependentSchemas.Value) + len(schema.PatternProperties.Value)
if totalProps+children > 0 { if totalProps+children > 0 {
allDone: allDone:
for true { for true {
@@ -332,9 +428,7 @@ func NewSchema(schema *base.Schema) *Schema {
s.OneOf = oneOf s.OneOf = oneOf
s.AnyOf = anyOf s.AnyOf = anyOf
s.AllOf = allOf s.AllOf = allOf
if len(items) > 0 { s.Items = items
s.Items = items[0]
}
s.PrefixItems = prefixItems s.PrefixItems = prefixItems
s.Not = not s.Not = not
return s return s

View File

@@ -195,7 +195,26 @@ deprecated: true
contains: contains:
type: int type: int
minContains: 1 minContains: 1
maxContains: 10` maxContains: 10
if:
type: string
else:
type: integer
then:
type: boolean
dependentSchemas:
schemaOne:
type: string
patternProperties:
patternOne:
type: string
propertyNames:
type: string
unevaluatedItems:
type: boolean
unevaluatedProperties:
type: integer
`
var compNode yaml.Node var compNode yaml.Node
_ = yaml.Unmarshal([]byte(testSpec), &compNode) _ = yaml.Unmarshal([]byte(testSpec), &compNode)
@@ -217,10 +236,18 @@ maxContains: 10`
assert.NotNil(t, compiled) assert.NotNil(t, compiled)
assert.Nil(t, schemaProxy.GetBuildError()) assert.Nil(t, schemaProxy.GetBuildError())
// check contains // check 3.1 properties
assert.Equal(t, "int", compiled.Contains.Schema().Type[0]) assert.Equal(t, "int", compiled.Contains.Schema().Type[0])
assert.Equal(t, int64(10), *compiled.MaxContains) assert.Equal(t, int64(10), *compiled.MaxContains)
assert.Equal(t, int64(1), *compiled.MinContains) assert.Equal(t, int64(1), *compiled.MinContains)
assert.Equal(t, "string", compiled.If.Schema().Type[0])
assert.Equal(t, "integer", compiled.Else.Schema().Type[0])
assert.Equal(t, "boolean", compiled.Then.Schema().Type[0])
assert.Equal(t, "string", compiled.PatternProperties["patternOne"].Schema().Type[0])
assert.Equal(t, "string", compiled.DependentSchemas["schemaOne"].Schema().Type[0])
assert.Equal(t, "string", compiled.PropertyNames.Schema().Type[0])
assert.Equal(t, "boolean", compiled.UnevaluatedItems.Schema().Type[0])
assert.Equal(t, "integer", compiled.UnevaluatedProperties.Schema().Type[0])
wentLow := compiled.GoLow() wentLow := compiled.GoLow()
assert.Equal(t, 114, wentLow.AdditionalProperties.ValueNode.Line) assert.Equal(t, 114, wentLow.AdditionalProperties.ValueNode.Line)
@@ -352,10 +379,9 @@ required: [cake, fish]`
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.ExclusiveMaximum.A)
assert.False(t, *compiled.ExclusiveMinimumBool) assert.Equal(t, int64(123), compiled.Properties["somethingB"].Schema().ExclusiveMinimum.B)
assert.Equal(t, int64(123), *compiled.Properties["somethingB"].Schema().ExclusiveMinimum) assert.Equal(t, int64(334), compiled.Properties["somethingB"].Schema().ExclusiveMaximum.B)
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()
@@ -433,10 +459,9 @@ type: number
assert.Nil(t, highSchema.MultipleOf) assert.Nil(t, highSchema.MultipleOf)
assert.Nil(t, highSchema.Minimum) assert.Nil(t, highSchema.Minimum)
assert.Nil(t, highSchema.ExclusiveMinimum) assert.Nil(t, highSchema.ExclusiveMinimum)
assert.Nil(t, highSchema.ExclusiveMinimumBool)
assert.Nil(t, highSchema.Maximum) assert.Nil(t, highSchema.Maximum)
assert.Nil(t, highSchema.ExclusiveMaximum) assert.Nil(t, highSchema.ExclusiveMaximum)
assert.Nil(t, highSchema.ExclusiveMaximumBool)
} }
func TestSchemaNumberMultipleOf(t *testing.T) { func TestSchemaNumberMultipleOf(t *testing.T) {
@@ -480,7 +505,7 @@ exclusiveMinimum: 5
highSchema := getHighSchema(t, yml) highSchema := getHighSchema(t, yml)
value := int64(5) value := int64(5)
assert.EqualValues(t, &value, highSchema.ExclusiveMinimum) assert.EqualValues(t, value, highSchema.ExclusiveMinimum.B)
} }
func TestSchemaNumberMaximum(t *testing.T) { func TestSchemaNumberMaximum(t *testing.T) {
@@ -513,7 +538,7 @@ exclusiveMaximum: 5
highSchema := getHighSchema(t, yml) highSchema := getHighSchema(t, yml)
value := int64(5) value := int64(5)
assert.EqualValues(t, &value, highSchema.ExclusiveMaximum) assert.EqualValues(t, value, highSchema.ExclusiveMaximum.B)
} }
func TestSchemaExamples(t *testing.T) { func TestSchemaExamples(t *testing.T) {

View File

@@ -203,7 +203,7 @@ func TestNewDocument_Components_Schemas(t *testing.T) {
assert.Equal(t, 445, b.Schema().GoLow().FindProperty("name").ValueNode.Line) assert.Equal(t, 445, b.Schema().GoLow().FindProperty("name").ValueNode.Line)
f := h.Components.Schemas["Fries"] f := h.Components.Schemas["Fries"]
assert.Equal(t, "salt", f.Schema().Properties["seasoning"].Schema().Items.Schema().Example) assert.Equal(t, "salt", f.Schema().Properties["seasoning"].Schema().Items.A.Schema().Example)
assert.Len(t, f.Schema().Properties["favoriteDrink"].Schema().Properties["drinkType"].Schema().Enum, 2) assert.Len(t, f.Schema().Properties["favoriteDrink"].Schema().Properties["drinkType"].Schema().Enum, 2)
d := h.Components.Schemas["Drink"] d := h.Components.Schemas["Drink"]

View File

@@ -14,6 +14,14 @@ const (
ContactLabel = "contact" ContactLabel = "contact"
LicenseLabel = "license" LicenseLabel = "license"
PropertiesLabel = "properties" PropertiesLabel = "properties"
DependentSchemasLabel = "dependentSchemas"
PatternPropertiesLabel = "patternProperties"
IfLabel = "if"
ElseLabel = "else"
ThenLabel = "then"
PropertyNamesLabel = "propertyNames"
UnevaluatedItemsLabel = "unevaluatedItems"
UnevaluatedPropertiesLabel = "unevaluatedProperties"
AdditionalPropertiesLabel = "additionalProperties" AdditionalPropertiesLabel = "additionalProperties"
XMLLabel = "xml" XMLLabel = "xml"
ItemsLabel = "items" ItemsLabel = "items"
@@ -30,3 +38,9 @@ const (
SchemaLabel = "schema" SchemaLabel = "schema"
SchemaTypeLabel = "$schema" SchemaTypeLabel = "$schema"
) )
/*
PropertyNames low.NodeReference[*SchemaProxy]
UnevaluatedItems low.NodeReference[*SchemaProxy]
UnevaluatedProperties low.NodeReference[*SchemaProxy]
*/

View File

@@ -51,10 +51,10 @@ type Schema struct {
SchemaTypeRef low.NodeReference[string] SchemaTypeRef low.NodeReference[string]
// In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean. // In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean.
ExclusiveMaximum low.NodeReference[SchemaDynamicValue[bool, int64]] ExclusiveMaximum low.NodeReference[*SchemaDynamicValue[bool, int64]]
// In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean. // In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean.
ExclusiveMinimum low.NodeReference[SchemaDynamicValue[bool, int64]] ExclusiveMinimum low.NodeReference[*SchemaDynamicValue[bool, int64]]
// In versions 2 and 3.0, this Type is a single value, so array will only ever have one value // 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 // in version 3.1, Type can be multiple values
@@ -77,6 +77,19 @@ type Schema struct {
MinContains low.NodeReference[int64] MinContains low.NodeReference[int64]
MaxContains low.NodeReference[int64] MaxContains low.NodeReference[int64]
// items can be a schema in 2.0, 3.0 and 3.1 or a bool in 3.1
Items low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]
// 3.1 only
If low.NodeReference[*SchemaProxy]
Else low.NodeReference[*SchemaProxy]
Then low.NodeReference[*SchemaProxy]
DependentSchemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
PatternProperties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
PropertyNames low.NodeReference[*SchemaProxy]
UnevaluatedItems low.NodeReference[*SchemaProxy]
UnevaluatedProperties low.NodeReference[*SchemaProxy]
// Compatible with all versions // Compatible with all versions
Title low.NodeReference[string] Title low.NodeReference[string]
MultipleOf low.NodeReference[int64] MultipleOf low.NodeReference[int64]
@@ -93,8 +106,7 @@ type Schema struct {
MinProperties low.NodeReference[int64] MinProperties low.NodeReference[int64]
Required low.NodeReference[[]low.ValueReference[string]] Required low.NodeReference[[]low.ValueReference[string]]
Enum low.NodeReference[[]low.ValueReference[any]] Enum low.NodeReference[[]low.ValueReference[any]]
Not low.NodeReference[[]low.ValueReference[*SchemaProxy]] Not low.NodeReference[*SchemaProxy]
Items low.NodeReference[[]low.ValueReference[*SchemaProxy]]
Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]] Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
AdditionalProperties low.NodeReference[any] AdditionalProperties low.NodeReference[any]
Description low.NodeReference[string] Description low.NodeReference[string]
@@ -229,18 +241,15 @@ func (s *Schema) Hash() [32]byte {
for i := range s.Enum.Value { for i := range s.Enum.Value {
d = append(d, fmt.Sprint(s.Enum.Value[i].Value)) d = append(d, fmt.Sprint(s.Enum.Value[i].Value))
} }
propertyKeys := make([]string, len(s.Properties.Value)) propKeys := make([]string, len(s.Properties.Value))
z := 0 z := 0
for i := range s.Properties.Value { for i := range s.Properties.Value {
propertyKeys[z] = i.Value propKeys[z] = i.Value
z++ z++
} }
sort.Strings(propertyKeys) sort.Strings(propKeys)
for k := range propertyKeys { for k := range propKeys {
prop := s.FindProperty(propertyKeys[k]).Value d = append(d, low.GenerateHashString(s.FindProperty(propKeys[k]).Value))
if !prop.IsSchemaReference() {
d = append(d, low.GenerateHashString(prop.Schema()))
}
} }
if s.XML.Value != nil { if s.XML.Value != nil {
d = append(d, low.GenerateHashString(s.XML.Value)) d = append(d, low.GenerateHashString(s.XML.Value))
@@ -307,39 +316,57 @@ func (s *Schema) Hash() [32]byte {
} }
} }
if len(s.Not.Value) > 0 { if !s.Not.IsEmpty() {
notKeys := make([]string, len(s.Not.Value)) d = append(d, low.GenerateHashString(s.Not.Value))
notEntities := make(map[string]*SchemaProxy)
z = 0
for i := range s.Not.Value {
g := s.Not.Value[i].Value
r := low.GenerateHashString(g)
notEntities[r] = g
notKeys[z] = r
z++
}
sort.Strings(notKeys)
for k := range notKeys {
d = append(d, low.GenerateHashString(notEntities[notKeys[k]]))
}
} }
if len(s.Items.Value) > 0 { // check if items is a schema or a bool.
itemsKeys := make([]string, len(s.Items.Value)) if !s.Items.IsEmpty() && s.Items.Value.IsA() {
itemsEntities := make(map[string]*SchemaProxy) d = append(d, low.GenerateHashString(s.Items.Value.A))
}
if !s.Items.IsEmpty() && s.Items.Value.IsB() {
d = append(d, fmt.Sprint(s.Items.Value.B))
}
// 3.1 only props
if !s.If.IsEmpty() {
d = append(d, low.GenerateHashString(s.If.Value))
}
if !s.Else.IsEmpty() {
d = append(d, low.GenerateHashString(s.Else.Value))
}
if !s.Then.IsEmpty() {
d = append(d, low.GenerateHashString(s.Then.Value))
}
if !s.PropertyNames.IsEmpty() {
d = append(d, low.GenerateHashString(s.PropertyNames.Value))
}
if !s.UnevaluatedProperties.IsEmpty() {
d = append(d, low.GenerateHashString(s.UnevaluatedProperties.Value))
}
if !s.UnevaluatedItems.IsEmpty() {
d = append(d, low.GenerateHashString(s.UnevaluatedItems.Value))
}
depSchemasKeys := make([]string, len(s.DependentSchemas.Value))
z = 0 z = 0
for i := range s.Items.Value { for i := range s.DependentSchemas.Value {
g := s.Items.Value[i].Value depSchemasKeys[z] = i.Value
r := low.GenerateHashString(g)
itemsEntities[r] = g
itemsKeys[z] = r
z++ z++
} }
sort.Strings(itemsKeys) sort.Strings(depSchemasKeys)
for k := range itemsKeys { for k := range depSchemasKeys {
d = append(d, low.GenerateHashString(itemsEntities[itemsKeys[k]])) d = append(d, low.GenerateHashString(s.FindDependentSchema(depSchemasKeys[k]).Value))
} }
patternPropsKeys := make([]string, len(s.PatternProperties.Value))
z = 0
for i := range s.PatternProperties.Value {
patternPropsKeys[z] = i.Value
z++
}
sort.Strings(patternPropsKeys)
for k := range patternPropsKeys {
d = append(d, low.GenerateHashString(s.FindPatternProperty(patternPropsKeys[k]).Value))
} }
if len(s.PrefixItems.Value) > 0 { if len(s.PrefixItems.Value) > 0 {
@@ -399,6 +426,18 @@ func (s *Schema) FindProperty(name string) *low.ValueReference[*SchemaProxy] {
return low.FindItemInMap[*SchemaProxy](name, s.Properties.Value) return low.FindItemInMap[*SchemaProxy](name, s.Properties.Value)
} }
// FindDependentSchema will return a ValueReference pointer containing a SchemaProxy pointer
// from a dependent schema key name. if found (3.1+ only)
func (s *Schema) FindDependentSchema(name string) *low.ValueReference[*SchemaProxy] {
return low.FindItemInMap[*SchemaProxy](name, s.DependentSchemas.Value)
}
// FindPatternProperty will return a ValueReference pointer containing a SchemaProxy pointer
// from a pattern property key name. if found (3.1+ only)
func (s *Schema) FindPatternProperty(name string) *low.ValueReference[*SchemaProxy] {
return low.FindItemInMap[*SchemaProxy](name, s.PatternProperties.Value)
}
// GetExtensions returns all extensions for Schema // GetExtensions returns all extensions for Schema
func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] { func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
return s.Extensions return s.Extensions
@@ -419,6 +458,14 @@ func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference
// - Not // - Not
// - Items // - Items
// - PrefixItems // - PrefixItems
// - If
// - Else
// - Then
// - DependentSchemas
// - PatternProperties
// - PropertyNames
// - UnevaluatedItems
// - UnevaluatedProperties
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if h, _, _ := utils.IsNodeRefValue(root); h { if h, _, _ := utils.IsNodeRefValue(root); h {
ref, err := low.LocateRefNode(root, idx) ref, err := low.LocateRefNode(root, idx)
@@ -469,18 +516,18 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if exMinValue != nil { if exMinValue != nil {
if utils.IsNodeBoolValue(exMinValue) { if utils.IsNodeBoolValue(exMinValue) {
val, _ := strconv.ParseBool(exMinValue.Value) val, _ := strconv.ParseBool(exMinValue.Value)
s.ExclusiveMinimum = low.NodeReference[SchemaDynamicValue[bool, int64]]{ s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, int64]]{
KeyNode: exMinLabel, KeyNode: exMinLabel,
ValueNode: exMinValue, ValueNode: exMinValue,
Value: SchemaDynamicValue[bool, int64]{N: 0, A: val}, Value: &SchemaDynamicValue[bool, int64]{N: 0, A: val},
} }
} }
if utils.IsNodeIntValue(exMinValue) { if utils.IsNodeIntValue(exMinValue) {
val, _ := strconv.ParseInt(exMinValue.Value, 10, 64) val, _ := strconv.ParseInt(exMinValue.Value, 10, 64)
s.ExclusiveMinimum = low.NodeReference[SchemaDynamicValue[bool, int64]]{ s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, int64]]{
KeyNode: exMinLabel, KeyNode: exMinLabel,
ValueNode: exMinValue, ValueNode: exMinValue,
Value: SchemaDynamicValue[bool, int64]{N: 1, B: val}, Value: &SchemaDynamicValue[bool, int64]{N: 1, B: val},
} }
} }
} }
@@ -490,18 +537,18 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if exMaxValue != nil { if exMaxValue != nil {
if utils.IsNodeBoolValue(exMaxValue) { if utils.IsNodeBoolValue(exMaxValue) {
val, _ := strconv.ParseBool(exMaxValue.Value) val, _ := strconv.ParseBool(exMaxValue.Value)
s.ExclusiveMaximum = low.NodeReference[SchemaDynamicValue[bool, int64]]{ s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, int64]]{
KeyNode: exMaxLabel, KeyNode: exMaxLabel,
ValueNode: exMaxValue, ValueNode: exMaxValue,
Value: SchemaDynamicValue[bool, int64]{N: 0, A: val}, Value: &SchemaDynamicValue[bool, int64]{N: 0, A: val},
} }
} }
if utils.IsNodeIntValue(exMaxValue) { if utils.IsNodeIntValue(exMaxValue) {
val, _ := strconv.ParseInt(exMaxValue.Value, 10, 64) val, _ := strconv.ParseInt(exMaxValue.Value, 10, 64)
s.ExclusiveMaximum = low.NodeReference[SchemaDynamicValue[bool, int64]]{ s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, int64]]{
KeyNode: exMaxLabel, KeyNode: exMaxLabel,
ValueNode: exMaxValue, ValueNode: exMaxValue,
Value: SchemaDynamicValue[bool, int64]{N: 1, B: val}, Value: &SchemaDynamicValue[bool, int64]{N: 1, B: val},
} }
} }
} }
@@ -585,77 +632,84 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode} s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode}
} }
// for property, build in a new thread! /*
bChan := make(chan schemaProxyBuildResult)
var buildProperty = func(label *yaml.Node, value *yaml.Node, c chan schemaProxyBuildResult, isRef bool, */
refString string) {
c <- schemaProxyBuildResult{
k: low.KeyReference[string]{
KeyNode: label,
Value: label.Value,
},
v: low.ValueReference[*SchemaProxy]{
Value: &SchemaProxy{kn: label, vn: value, idx: idx, isReference: isRef, referenceLookup: refString},
ValueNode: value,
},
}
}
// handle properties // handle properties
_, propLabel, propsNode := utils.FindKeyNodeFullTop(PropertiesLabel, root.Content) props, err := buildPropertyMap(root, idx, PropertiesLabel)
if propsNode != nil { if err != nil {
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]) return err
var currentProp *yaml.Node }
totalProps := 0 if props != nil {
for i, prop := range propsNode.Content { s.Properties = *props
if i%2 == 0 {
currentProp = prop
continue
} }
// check our prop isn't reference // handle dependent schemas
isRef := false props, err = buildPropertyMap(root, idx, DependentSchemasLabel)
refString := "" if err != nil {
if h, _, l := utils.IsNodeRefValue(prop); h { return err
ref, _ := low.LocateRefNode(prop, idx) }
if ref != nil { if props != nil {
isRef = true s.DependentSchemas = *props
prop = ref }
refString = l
} else { // handle pattern properties
return fmt.Errorf("schema properties build failed: cannot find reference %s, line %d, col %d", props, err = buildPropertyMap(root, idx, PatternPropertiesLabel)
prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line) if err != nil {
return err
}
if props != nil {
s.PatternProperties = *props
}
// check items type for schema or bool (3.1 only)
itemsIsBool := false
itemsBoolValue := false
_, itemsLabel, itemsValue := utils.FindKeyNodeFullTop(ItemsLabel, root.Content)
if itemsValue != nil {
if utils.IsNodeBoolValue(itemsValue) {
itemsIsBool = true
itemsBoolValue, _ = strconv.ParseBool(itemsValue.Value)
} }
} }
totalProps++ if itemsIsBool {
go buildProperty(currentProp, prop, bChan, isRef, refString) s.Items = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]{
} Value: &SchemaDynamicValue[*SchemaProxy, bool]{
completedProps := 0 B: itemsBoolValue,
for completedProps < totalProps { N: 1,
select { },
case res := <-bChan: KeyNode: itemsLabel,
completedProps++ ValueNode: itemsValue,
propertyMap[res.k] = res.v
}
}
s.Properties = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]{
Value: propertyMap,
KeyNode: propLabel,
ValueNode: propsNode,
} }
} }
var allOf, anyOf, oneOf, not, items, prefixItems []low.ValueReference[*SchemaProxy] /*
var contains low.ValueReference[*SchemaProxy] If low.NodeReference[*SchemaProxy]
Else low.NodeReference[*SchemaProxy]
Then low.NodeReference[*SchemaProxy]
PropertyNames low.NodeReference[*SchemaProxy]
UnevaluatedItems low.NodeReference[*SchemaProxy]
UnevaluatedProperties low.NodeReference[*SchemaProxy]
*/
var allOf, anyOf, oneOf, prefixItems []low.ValueReference[*SchemaProxy]
var items, not, contains, sif, selse, sthen, propertyNames, unevalItems, unevalProperties low.ValueReference[*SchemaProxy]
_, allOfLabel, allOfValue := utils.FindKeyNodeFullTop(AllOfLabel, root.Content) _, allOfLabel, allOfValue := utils.FindKeyNodeFullTop(AllOfLabel, root.Content)
_, anyOfLabel, anyOfValue := utils.FindKeyNodeFullTop(AnyOfLabel, root.Content) _, anyOfLabel, anyOfValue := utils.FindKeyNodeFullTop(AnyOfLabel, root.Content)
_, oneOfLabel, oneOfValue := utils.FindKeyNodeFullTop(OneOfLabel, root.Content) _, oneOfLabel, oneOfValue := utils.FindKeyNodeFullTop(OneOfLabel, root.Content)
_, notLabel, notValue := utils.FindKeyNodeFullTop(NotLabel, root.Content) _, notLabel, notValue := utils.FindKeyNodeFullTop(NotLabel, root.Content)
_, itemsLabel, itemsValue := utils.FindKeyNodeFullTop(ItemsLabel, root.Content)
_, prefixItemsLabel, prefixItemsValue := utils.FindKeyNodeFullTop(PrefixItemsLabel, root.Content) _, prefixItemsLabel, prefixItemsValue := utils.FindKeyNodeFullTop(PrefixItemsLabel, root.Content)
_, containsLabel, containsValue := utils.FindKeyNodeFullTop(ContainsLabel, root.Content) _, containsLabel, containsValue := utils.FindKeyNodeFullTop(ContainsLabel, root.Content)
_, sifLabel, sifValue := utils.FindKeyNodeFullTop(IfLabel, root.Content)
_, selseLabel, selseValue := utils.FindKeyNodeFullTop(ElseLabel, root.Content)
_, sthenLabel, sthenValue := utils.FindKeyNodeFullTop(ThenLabel, root.Content)
_, propNamesLabel, propNamesValue := utils.FindKeyNodeFullTop(PropertyNamesLabel, root.Content)
_, unevalItemsLabel, unevalItemsValue := utils.FindKeyNodeFullTop(UnevaluatedItemsLabel, root.Content)
_, unevalPropsLabel, unevalPropsValue := utils.FindKeyNodeFullTop(UnevaluatedPropertiesLabel, root.Content)
errorChan := make(chan error) errorChan := make(chan error)
allOfChan := make(chan schemaProxyBuildResult) allOfChan := make(chan schemaProxyBuildResult)
@@ -665,12 +719,16 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
prefixItemsChan := make(chan schemaProxyBuildResult) prefixItemsChan := make(chan schemaProxyBuildResult)
notChan := make(chan schemaProxyBuildResult) notChan := make(chan schemaProxyBuildResult)
containsChan := make(chan schemaProxyBuildResult) containsChan := make(chan schemaProxyBuildResult)
ifChan := make(chan schemaProxyBuildResult)
elseChan := make(chan schemaProxyBuildResult)
thenChan := make(chan schemaProxyBuildResult)
propNamesChan := make(chan schemaProxyBuildResult)
unevalItemsChan := make(chan schemaProxyBuildResult)
unevalPropsChan := make(chan schemaProxyBuildResult)
totalBuilds := countSubSchemaItems(allOfValue) + totalBuilds := countSubSchemaItems(allOfValue) +
countSubSchemaItems(anyOfValue) + countSubSchemaItems(anyOfValue) +
countSubSchemaItems(oneOfValue) + countSubSchemaItems(oneOfValue) +
countSubSchemaItems(notValue) +
countSubSchemaItems(itemsValue) +
countSubSchemaItems(prefixItemsValue) countSubSchemaItems(prefixItemsValue)
if allOfValue != nil { if allOfValue != nil {
@@ -682,19 +740,45 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if oneOfValue != nil { if oneOfValue != nil {
go buildSchema(oneOfChan, oneOfLabel, oneOfValue, errorChan, idx) go buildSchema(oneOfChan, oneOfLabel, oneOfValue, errorChan, idx)
} }
if itemsValue != nil {
go buildSchema(itemsChan, itemsLabel, itemsValue, errorChan, idx)
}
if prefixItemsValue != nil { if prefixItemsValue != nil {
go buildSchema(prefixItemsChan, prefixItemsLabel, prefixItemsValue, errorChan, idx) go buildSchema(prefixItemsChan, prefixItemsLabel, prefixItemsValue, errorChan, idx)
} }
if notValue != nil { if notValue != nil {
totalBuilds++
go buildSchema(notChan, notLabel, notValue, errorChan, idx) go buildSchema(notChan, notLabel, notValue, errorChan, idx)
} }
if containsValue != nil { if containsValue != nil {
totalBuilds++ totalBuilds++
go buildSchema(containsChan, containsLabel, containsValue, errorChan, idx) go buildSchema(containsChan, containsLabel, containsValue, errorChan, idx)
} }
if !itemsIsBool && itemsValue != nil {
totalBuilds++
go buildSchema(itemsChan, itemsLabel, itemsValue, errorChan, idx)
}
if sifValue != nil {
totalBuilds++
go buildSchema(ifChan, sifLabel, sifValue, errorChan, idx)
}
if selseValue != nil {
totalBuilds++
go buildSchema(elseChan, selseLabel, selseValue, errorChan, idx)
}
if sthenValue != nil {
totalBuilds++
go buildSchema(thenChan, sthenLabel, sthenValue, errorChan, idx)
}
if propNamesValue != nil {
totalBuilds++
go buildSchema(propNamesChan, propNamesLabel, propNamesValue, errorChan, idx)
}
if unevalItemsValue != nil {
totalBuilds++
go buildSchema(unevalItemsChan, unevalItemsLabel, unevalItemsValue, errorChan, idx)
}
if unevalPropsValue != nil {
totalBuilds++
go buildSchema(unevalPropsChan, unevalPropsLabel, unevalPropsValue, errorChan, idx)
}
completeCount := 0 completeCount := 0
for completeCount < totalBuilds { for completeCount < totalBuilds {
@@ -712,16 +796,34 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
oneOf = append(oneOf, r.v) oneOf = append(oneOf, r.v)
case r := <-itemsChan: case r := <-itemsChan:
completeCount++ completeCount++
items = append(items, r.v) items = r.v
case r := <-prefixItemsChan: case r := <-prefixItemsChan:
completeCount++ completeCount++
prefixItems = append(prefixItems, r.v) prefixItems = append(prefixItems, r.v)
case r := <-notChan: case r := <-notChan:
completeCount++ completeCount++
not = append(not, r.v) not = r.v
case r := <-containsChan: case r := <-containsChan:
completeCount++ completeCount++
contains = r.v contains = r.v
case r := <-ifChan:
completeCount++
sif = r.v
case r := <-elseChan:
completeCount++
selse = r.v
case r := <-thenChan:
completeCount++
sthen = r.v
case r := <-propNamesChan:
completeCount++
propertyNames = r.v
case r := <-unevalItemsChan:
completeCount++
unevalItems = r.v
case r := <-unevalPropsChan:
completeCount++
unevalProperties = r.v
} }
} }
@@ -746,17 +848,18 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
ValueNode: allOfValue, ValueNode: allOfValue,
} }
} }
if len(not) > 0 { if !not.IsEmpty() {
s.Not = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{ s.Not = low.NodeReference[*SchemaProxy]{
Value: not, Value: not.Value,
KeyNode: notLabel, KeyNode: notLabel,
ValueNode: notValue, ValueNode: notValue,
} }
} }
if len(items) > 0 { if !itemsIsBool && !items.IsEmpty() {
s.Items = low.NodeReference[[]low.ValueReference[*SchemaProxy]]{ s.Items = low.NodeReference[*SchemaDynamicValue[*SchemaProxy, bool]]{
Value: items, Value: &SchemaDynamicValue[*SchemaProxy, bool]{
A: items.Value,
},
KeyNode: itemsLabel, KeyNode: itemsLabel,
ValueNode: itemsValue, ValueNode: itemsValue,
} }
@@ -775,9 +878,115 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
ValueNode: containsValue, ValueNode: containsValue,
} }
} }
if !sif.IsEmpty() {
s.If = low.NodeReference[*SchemaProxy]{
Value: sif.Value,
KeyNode: sifLabel,
ValueNode: sifValue,
}
}
if !selse.IsEmpty() {
s.Else = low.NodeReference[*SchemaProxy]{
Value: selse.Value,
KeyNode: selseLabel,
ValueNode: selseValue,
}
}
if !sthen.IsEmpty() {
s.Then = low.NodeReference[*SchemaProxy]{
Value: sthen.Value,
KeyNode: sthenLabel,
ValueNode: sthenValue,
}
}
if !propertyNames.IsEmpty() {
s.PropertyNames = low.NodeReference[*SchemaProxy]{
Value: propertyNames.Value,
KeyNode: propNamesLabel,
ValueNode: propNamesValue,
}
}
if !unevalItems.IsEmpty() {
s.UnevaluatedItems = low.NodeReference[*SchemaProxy]{
Value: unevalItems.Value,
KeyNode: unevalItemsLabel,
ValueNode: unevalItemsValue,
}
}
if !unevalProperties.IsEmpty() {
s.UnevaluatedProperties = low.NodeReference[*SchemaProxy]{
Value: unevalProperties.Value,
KeyNode: unevalPropsLabel,
ValueNode: unevalPropsValue,
}
}
return nil return nil
} }
func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]], error) {
// for property, build in a new thread!
bChan := make(chan schemaProxyBuildResult)
var buildProperty = func(label *yaml.Node, value *yaml.Node, c chan schemaProxyBuildResult, isRef bool,
refString string) {
c <- schemaProxyBuildResult{
k: low.KeyReference[string]{
KeyNode: label,
Value: label.Value,
},
v: low.ValueReference[*SchemaProxy]{
Value: &SchemaProxy{kn: label, vn: value, idx: idx, isReference: isRef, referenceLookup: refString},
ValueNode: value,
},
}
}
_, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content)
if propsNode != nil {
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*SchemaProxy])
var currentProp *yaml.Node
totalProps := 0
for i, prop := range propsNode.Content {
if i%2 == 0 {
currentProp = prop
continue
}
// check our prop isn't reference
isRef := false
refString := ""
if h, _, l := utils.IsNodeRefValue(prop); h {
ref, _ := low.LocateRefNode(prop, idx)
if ref != nil {
isRef = true
prop = ref
refString = l
} else {
return nil, fmt.Errorf("schema properties build failed: cannot find reference %s, line %d, col %d",
prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line)
}
}
totalProps++
go buildProperty(currentProp, prop, bChan, isRef, refString)
}
completedProps := 0
for completedProps < totalProps {
select {
case res := <-bChan:
completedProps++
propertyMap[res.k] = res.v
}
}
return &low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]{
Value: propertyMap,
KeyNode: propLabel,
ValueNode: propsNode,
}, nil
}
return nil, nil
}
// count the number of sub-schemas in a node. // count the number of sub-schemas in a node.
func countSubSchemaItems(node *yaml.Node) int { func countSubSchemaItems(node *yaml.Node) int {
if utils.IsNodeMap(node) { if utils.IsNodeMap(node) {
@@ -894,7 +1103,6 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
} }
} }
} }
} }
} }

View File

@@ -12,6 +12,24 @@ import (
func test_get_schema_blob() string { func test_get_schema_blob() string {
return `type: object return `type: object
description: something object description: something object
if:
type: string
else:
type: integer
then:
type: boolean
dependentSchemas:
schemaOne:
type: string
patternProperties:
patternOne:
type: string
propertyNames:
type: string
unevaluatedItems:
type: boolean
unevaluatedProperties:
type: integer
discriminator: discriminator:
propertyName: athing propertyName: athing
mapping: mapping:
@@ -226,29 +244,29 @@ func Test_Schema(t *testing.T) {
assert.Equal(t, "oneOfBExp", v.Value.Schema().Example.Value) assert.Equal(t, "oneOfBExp", v.Value.Schema().Example.Value)
// check values NOT // check values NOT
assert.Equal(t, "a not thing", sch.Not.Value[0].Value.Schema().Description.Value) assert.Equal(t, "a not thing", sch.Not.Value.Schema().Description.Value)
assert.Len(t, sch.Not.Value[0].Value.Schema().Properties.Value, 2) assert.Len(t, sch.Not.Value.Schema().Properties.Value, 2)
v = sch.Not.Value[0].Value.Schema().FindProperty("notA") v = sch.Not.Value.Schema().FindProperty("notA")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "notA description", v.Value.Schema().Description.Value) assert.Equal(t, "notA description", v.Value.Schema().Description.Value)
assert.Equal(t, "notAExp", v.Value.Schema().Example.Value) assert.Equal(t, "notAExp", v.Value.Schema().Example.Value)
v = sch.Not.Value[0].Value.Schema().FindProperty("notB") v = sch.Not.Value.Schema().FindProperty("notB")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "notB description", v.Value.Schema().Description.Value) assert.Equal(t, "notB description", v.Value.Schema().Description.Value)
assert.Equal(t, "notBExp", v.Value.Schema().Example.Value) assert.Equal(t, "notBExp", v.Value.Schema().Example.Value)
// check values Items // check values Items
assert.Equal(t, "an items thing", sch.Items.Value[0].Value.Schema().Description.Value) assert.Equal(t, "an items thing", sch.Items.Value.A.Schema().Description.Value)
assert.Len(t, sch.Items.Value[0].Value.Schema().Properties.Value, 2) assert.Len(t, sch.Items.Value.A.Schema().Properties.Value, 2)
v = sch.Items.Value[0].Value.Schema().FindProperty("itemsA") v = sch.Items.Value.A.Schema().FindProperty("itemsA")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value) assert.Equal(t, "itemsA description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsAExp", v.Value.Schema().Example.Value) assert.Equal(t, "itemsAExp", v.Value.Schema().Example.Value)
v = sch.Items.Value[0].Value.Schema().FindProperty("itemsB") v = sch.Items.Value.A.Schema().FindProperty("itemsB")
assert.NotNil(t, v) assert.NotNil(t, v)
assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value) assert.Equal(t, "itemsB description", v.Value.Schema().Description.Value)
assert.Equal(t, "itemsBExp", v.Value.Schema().Example.Value) assert.Equal(t, "itemsBExp", v.Value.Schema().Example.Value)
@@ -276,10 +294,18 @@ func Test_Schema(t *testing.T) {
mv = sch.Discriminator.Value.FindMappingValue("pizza") mv = sch.Discriminator.Value.FindMappingValue("pizza")
assert.Equal(t, "party", mv.Value) assert.Equal(t, "party", mv.Value)
// check contains // check 3.1 properties.
assert.Equal(t, "int", sch.Contains.Value.Schema().Type.Value.A) assert.Equal(t, "int", sch.Contains.Value.Schema().Type.Value.A)
assert.Equal(t, int64(1), sch.MinContains.Value) assert.Equal(t, int64(1), sch.MinContains.Value)
assert.Equal(t, int64(10), sch.MaxContains.Value) assert.Equal(t, int64(10), sch.MaxContains.Value)
assert.Equal(t, "string", sch.If.Value.Schema().Type.Value.A)
assert.Equal(t, "integer", sch.Else.Value.Schema().Type.Value.A)
assert.Equal(t, "boolean", sch.Then.Value.Schema().Type.Value.A)
assert.Equal(t, "string", sch.FindDependentSchema("schemaOne").Value.Schema().Type.Value.A)
assert.Equal(t, "string", sch.FindPatternProperty("patternOne").Value.Schema().Type.Value.A)
assert.Equal(t, "string", sch.PropertyNames.Value.Schema().Type.Value.A)
assert.Equal(t, "boolean", sch.UnevaluatedItems.Value.Schema().Type.Value.A)
assert.Equal(t, "integer", sch.UnevaluatedProperties.Value.Schema().Type.Value.A)
} }
@@ -581,8 +607,8 @@ items:
assert.Equal(t, desc, sch.OneOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.OneOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.AllOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Not.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.Not.Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Items.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.Items.Value.A.Schema().Description.Value)
} }
func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) { func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) {
@@ -671,8 +697,8 @@ items:
assert.Equal(t, desc, sch.OneOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.OneOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.AnyOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.AllOf.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.AllOf.Value[0].Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Not.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.Not.Value.Schema().Description.Value)
assert.Equal(t, desc, sch.Items.Value[0].Value.Schema().Description.Value) assert.Equal(t, desc, sch.Items.Value.A.Schema().Description.Value)
} }
func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) { func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) {

View File

@@ -124,4 +124,15 @@ const (
ProducesLabel = "produces" ProducesLabel = "produces"
ConsumesLabel = "consumes" ConsumesLabel = "consumes"
SchemesLabel = "schemes" SchemesLabel = "schemes"
IfLabel = "if"
ElseLabel = "else"
ThenLabel = "then"
PropertyNamesLabel = "propertyNames"
ContainsLabel = "contains"
MinContainsLabel = "minContains"
MaxContainsLabel = "maxContains"
UnevaluatedItemsLabel = "unevaluatedItems"
UnevaluatedPropertiesLabel = "unevaluatedProperties"
DependentSchemasLabel = "dependentSchemas"
PatternPropertiesLabel = "patternProperties"
) )

View File

@@ -26,12 +26,23 @@ type SchemaChanges struct {
AllOfChanges []*SchemaChanges `json:"allOf,omitempty" yaml:"allOf,omitempty"` AllOfChanges []*SchemaChanges `json:"allOf,omitempty" yaml:"allOf,omitempty"`
AnyOfChanges []*SchemaChanges `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` AnyOfChanges []*SchemaChanges `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
OneOfChanges []*SchemaChanges `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` OneOfChanges []*SchemaChanges `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
NotChanges []*SchemaChanges `json:"not,omitempty" yaml:"not,omitempty"` NotChanges *SchemaChanges `json:"not,omitempty" yaml:"not,omitempty"`
ItemsChanges []*SchemaChanges `json:"items,omitempty" yaml:"items,omitempty"` ItemsChanges *SchemaChanges `json:"items,omitempty" yaml:"items,omitempty"`
SchemaPropertyChanges map[string]*SchemaChanges `json:"properties,omitempty" yaml:"properties,omitempty"` SchemaPropertyChanges map[string]*SchemaChanges `json:"properties,omitempty" yaml:"properties,omitempty"`
ExternalDocChanges *ExternalDocChanges `json:"externalDoc,omitempty" yaml:"externalDoc,omitempty"` ExternalDocChanges *ExternalDocChanges `json:"externalDoc,omitempty" yaml:"externalDoc,omitempty"`
XMLChanges *XMLChanges `json:"xml,omitempty" yaml:"xml,omitempty"` XMLChanges *XMLChanges `json:"xml,omitempty" yaml:"xml,omitempty"`
ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"` ExtensionChanges *ExtensionChanges `json:"extensions,omitempty" yaml:"extensions,omitempty"`
// 3.1 specifics
IfChanges *SchemaChanges `json:"if,omitempty" yaml:"if,omitempty"`
ElseChanges *SchemaChanges `json:"else,omitempty" yaml:"else,omitempty"`
ThenChanges *SchemaChanges `json:"then,omitempty" yaml:"then,omitempty"`
PropertyNamesChanges *SchemaChanges `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"`
ContainsChanges *SchemaChanges `json:"contains,omitempty" yaml:"contains,omitempty"`
UnevaluatedItemsChanges *SchemaChanges `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"`
UnevaluatedPropertiesChanges *SchemaChanges `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"`
DependentSchemasChanges map[string]*SchemaChanges `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"`
PatternPropertiesChanges map[string]*SchemaChanges `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"`
} }
// TotalChanges returns a count of the total number of changes made to this schema and all sub-schemas // TotalChanges returns a count of the total number of changes made to this schema and all sub-schemas
@@ -55,21 +66,48 @@ func (s *SchemaChanges) TotalChanges() int {
t += s.OneOfChanges[n].TotalChanges() t += s.OneOfChanges[n].TotalChanges()
} }
} }
if len(s.NotChanges) > 0 { if s.NotChanges != nil {
for n := range s.NotChanges { t += s.NotChanges.TotalChanges()
t += s.NotChanges[n].TotalChanges()
} }
if s.ItemsChanges != nil {
t += s.ItemsChanges.TotalChanges()
} }
if len(s.ItemsChanges) > 0 { if s.IfChanges != nil {
for n := range s.ItemsChanges { t += s.IfChanges.TotalChanges()
t += s.ItemsChanges[n].TotalChanges()
} }
if s.ElseChanges != nil {
t += s.ElseChanges.TotalChanges()
}
if s.ThenChanges != nil {
t += s.ThenChanges.TotalChanges()
}
if s.PropertyNamesChanges != nil {
t += s.PropertyNamesChanges.TotalChanges()
}
if s.ContainsChanges != nil {
t += s.ContainsChanges.TotalChanges()
}
if s.UnevaluatedItemsChanges != nil {
t += s.UnevaluatedItemsChanges.TotalChanges()
}
if s.UnevaluatedPropertiesChanges != nil {
t += s.UnevaluatedPropertiesChanges.TotalChanges()
} }
if s.SchemaPropertyChanges != nil { if s.SchemaPropertyChanges != nil {
for n := range s.SchemaPropertyChanges { for n := range s.SchemaPropertyChanges {
t += s.SchemaPropertyChanges[n].TotalChanges() t += s.SchemaPropertyChanges[n].TotalChanges()
} }
} }
if s.DependentSchemasChanges != nil {
for n := range s.DependentSchemasChanges {
t += s.DependentSchemasChanges[n].TotalChanges()
}
}
if s.PatternPropertiesChanges != nil {
for n := range s.PatternPropertiesChanges {
t += s.PatternPropertiesChanges[n].TotalChanges()
}
}
if s.ExternalDocChanges != nil { if s.ExternalDocChanges != nil {
t += s.ExternalDocChanges.TotalChanges() t += s.ExternalDocChanges.TotalChanges()
} }
@@ -108,14 +146,41 @@ func (s *SchemaChanges) TotalBreakingChanges() int {
t += s.OneOfChanges[n].TotalBreakingChanges() t += s.OneOfChanges[n].TotalBreakingChanges()
} }
} }
if len(s.NotChanges) > 0 { if s.NotChanges != nil {
for n := range s.NotChanges { t += s.NotChanges.TotalBreakingChanges()
t += s.NotChanges[n].TotalBreakingChanges() }
if s.ItemsChanges != nil {
t += s.ItemsChanges.TotalBreakingChanges()
}
if s.IfChanges != nil {
t += s.IfChanges.TotalBreakingChanges()
}
if s.ElseChanges != nil {
t += s.ElseChanges.TotalBreakingChanges()
}
if s.ThenChanges != nil {
t += s.ThenChanges.TotalBreakingChanges()
}
if s.PropertyNamesChanges != nil {
t += s.PropertyNamesChanges.TotalBreakingChanges()
}
if s.ContainsChanges != nil {
t += s.ContainsChanges.TotalBreakingChanges()
}
if s.UnevaluatedItemsChanges != nil {
t += s.UnevaluatedItemsChanges.TotalBreakingChanges()
}
if s.UnevaluatedPropertiesChanges != nil {
t += s.UnevaluatedPropertiesChanges.TotalBreakingChanges()
}
if s.DependentSchemasChanges != nil {
for n := range s.DependentSchemasChanges {
t += s.DependentSchemasChanges[n].TotalBreakingChanges()
} }
} }
if len(s.ItemsChanges) > 0 { if s.PatternPropertiesChanges != nil {
for n := range s.ItemsChanges { for n := range s.PatternPropertiesChanges {
t += s.ItemsChanges[n].TotalBreakingChanges() t += s.PatternPropertiesChanges[n].TotalBreakingChanges()
} }
} }
if s.XMLChanges != nil { if s.XMLChanges != nil {
@@ -215,13 +280,13 @@ func CompareSchemas(l, r *base.SchemaProxy) *SchemaChanges {
go extractSchemaChanges(lSchema.AnyOf.Value, rSchema.AnyOf.Value, v3.AnyOfLabel, go extractSchemaChanges(lSchema.AnyOf.Value, rSchema.AnyOf.Value, v3.AnyOfLabel,
&sc.AnyOfChanges, &changes, doneChan) &sc.AnyOfChanges, &changes, doneChan)
go extractSchemaChanges(lSchema.Items.Value, rSchema.Items.Value, v3.ItemsLabel, //go extractSchemaChanges(lSchema.Items.Value, rSchema.Items.Value, v3.ItemsLabel,
&sc.ItemsChanges, &changes, doneChan) // &sc.ItemsChanges, &changes, doneChan)
//
//go extractSchemaChanges(lSchema.Not.Value, rSchema.Not.Value, v3.NotLabel,
// &sc.NotChanges, &changes, doneChan)
go extractSchemaChanges(lSchema.Not.Value, rSchema.Not.Value, v3.NotLabel, totalChecks := totalProperties + 3
&sc.NotChanges, &changes, doneChan)
totalChecks := totalProperties + 5
completedChecks := 0 completedChecks := 0
for completedChecks < totalChecks { for completedChecks < totalChecks {
select { select {
@@ -752,6 +817,159 @@ func checkSchemaPropertyChanges(
lSchema.ExternalDocs.ValueNode, nil, false, lSchema.ExternalDocs.Value, nil) lSchema.ExternalDocs.ValueNode, nil, false, lSchema.ExternalDocs.Value, nil)
} }
// 3.1 properties
// If
if lSchema.If.Value != nil && rSchema.If.Value != nil {
if !low.AreEqual(lSchema.If.Value, rSchema.If.Value) {
sc.IfChanges = CompareSchemas(lSchema.If.Value, rSchema.If.Value)
}
}
// added If
if lSchema.If.Value == nil && rSchema.If.Value != nil {
CreateChange(changes, ObjectAdded, v3.IfLabel,
nil, rSchema.If.ValueNode, true, nil, rSchema.If.Value)
}
// removed If
if lSchema.If.Value != nil && rSchema.If.Value == nil {
CreateChange(changes, ObjectRemoved, v3.IfLabel,
lSchema.If.ValueNode, nil, true, lSchema.If.Value, nil)
}
// Else
if lSchema.Else.Value != nil && rSchema.Else.Value != nil {
if !low.AreEqual(lSchema.Else.Value, rSchema.Else.Value) {
sc.ElseChanges = CompareSchemas(lSchema.Else.Value, rSchema.Else.Value)
}
}
// added Else
if lSchema.Else.Value == nil && rSchema.Else.Value != nil {
CreateChange(changes, ObjectAdded, v3.ElseLabel,
nil, rSchema.Else.ValueNode, true, nil, rSchema.Else.Value)
}
// removed Else
if lSchema.Else.Value != nil && rSchema.Else.Value == nil {
CreateChange(changes, ObjectRemoved, v3.ElseLabel,
lSchema.Else.ValueNode, nil, true, lSchema.Else.Value, nil)
}
// Then
if lSchema.Then.Value != nil && rSchema.Then.Value != nil {
if !low.AreEqual(lSchema.Then.Value, rSchema.Then.Value) {
sc.ThenChanges = CompareSchemas(lSchema.Then.Value, rSchema.Then.Value)
}
}
// added Then
if lSchema.Then.Value == nil && rSchema.Then.Value != nil {
CreateChange(changes, ObjectAdded, v3.ThenLabel,
nil, rSchema.Then.ValueNode, true, nil, rSchema.Then.Value)
}
// removed Then
if lSchema.Then.Value != nil && rSchema.Then.Value == nil {
CreateChange(changes, ObjectRemoved, v3.ThenLabel,
lSchema.Then.ValueNode, nil, true, lSchema.Then.Value, nil)
}
// PropertyNames
if lSchema.PropertyNames.Value != nil && rSchema.PropertyNames.Value != nil {
if !low.AreEqual(lSchema.PropertyNames.Value, rSchema.PropertyNames.Value) {
sc.PropertyNamesChanges = CompareSchemas(lSchema.PropertyNames.Value, rSchema.PropertyNames.Value)
}
}
// added PropertyNames
if lSchema.PropertyNames.Value == nil && rSchema.PropertyNames.Value != nil {
CreateChange(changes, ObjectAdded, v3.PropertyNamesLabel,
nil, rSchema.PropertyNames.ValueNode, true, nil, rSchema.PropertyNames.Value)
}
// removed PropertyNames
if lSchema.PropertyNames.Value != nil && rSchema.PropertyNames.Value == nil {
CreateChange(changes, ObjectRemoved, v3.PropertyNamesLabel,
lSchema.PropertyNames.ValueNode, nil, true, lSchema.PropertyNames.Value, nil)
}
// Contains
if lSchema.Contains.Value != nil && rSchema.Contains.Value != nil {
if !low.AreEqual(lSchema.Contains.Value, rSchema.Contains.Value) {
sc.ContainsChanges = CompareSchemas(lSchema.Contains.Value, rSchema.Contains.Value)
}
}
// added Contains
if lSchema.Contains.Value == nil && rSchema.Contains.Value != nil {
CreateChange(changes, ObjectAdded, v3.ContainsLabel,
nil, rSchema.Contains.ValueNode, true, nil, rSchema.Contains.Value)
}
// removed Contains
if lSchema.Contains.Value != nil && rSchema.Contains.Value == nil {
CreateChange(changes, ObjectRemoved, v3.ContainsLabel,
lSchema.Contains.ValueNode, nil, true, lSchema.Contains.Value, nil)
}
// UnevaluatedItems
if lSchema.UnevaluatedItems.Value != nil && rSchema.UnevaluatedItems.Value != nil {
if !low.AreEqual(lSchema.UnevaluatedItems.Value, rSchema.UnevaluatedItems.Value) {
sc.UnevaluatedItemsChanges = CompareSchemas(lSchema.UnevaluatedItems.Value, rSchema.UnevaluatedItems.Value)
}
}
// added UnevaluatedItems
if lSchema.UnevaluatedItems.Value == nil && rSchema.UnevaluatedItems.Value != nil {
CreateChange(changes, ObjectAdded, v3.UnevaluatedItemsLabel,
nil, rSchema.UnevaluatedItems.ValueNode, true, nil, rSchema.UnevaluatedItems.Value)
}
// removed UnevaluatedItems
if lSchema.UnevaluatedItems.Value != nil && rSchema.UnevaluatedItems.Value == nil {
CreateChange(changes, ObjectRemoved, v3.UnevaluatedItemsLabel,
lSchema.UnevaluatedItems.ValueNode, nil, true, lSchema.UnevaluatedItems.Value, nil)
}
// UnevaluatedProperties
if lSchema.UnevaluatedProperties.Value != nil && rSchema.UnevaluatedProperties.Value != nil {
if !low.AreEqual(lSchema.UnevaluatedProperties.Value, rSchema.UnevaluatedProperties.Value) {
sc.UnevaluatedPropertiesChanges = CompareSchemas(lSchema.UnevaluatedProperties.Value, rSchema.UnevaluatedProperties.Value)
}
}
// added UnevaluatedProperties
if lSchema.UnevaluatedProperties.Value == nil && rSchema.UnevaluatedProperties.Value != nil {
CreateChange(changes, ObjectAdded, v3.UnevaluatedPropertiesLabel,
nil, rSchema.UnevaluatedProperties.ValueNode, true, nil, rSchema.UnevaluatedProperties.Value)
}
// removed UnevaluatedProperties
if lSchema.UnevaluatedProperties.Value != nil && rSchema.UnevaluatedProperties.Value == nil {
CreateChange(changes, ObjectRemoved, v3.UnevaluatedPropertiesLabel,
lSchema.UnevaluatedProperties.ValueNode, nil, true, lSchema.UnevaluatedProperties.Value, nil)
}
// Not
if lSchema.Not.Value != nil && rSchema.Not.Value != nil {
if !low.AreEqual(lSchema.Not.Value, rSchema.Not.Value) {
sc.NotChanges = CompareSchemas(lSchema.Not.Value, rSchema.Not.Value)
}
}
// added Not
if lSchema.Not.Value == nil && rSchema.Not.Value != nil {
CreateChange(changes, ObjectAdded, v3.NotLabel,
nil, rSchema.Not.ValueNode, true, nil, rSchema.Not.Value)
}
// removed not
if lSchema.Not.Value != nil && rSchema.Not.Value == nil {
CreateChange(changes, ObjectRemoved, v3.NotLabel,
lSchema.Not.ValueNode, nil, true, lSchema.Not.Value, nil)
}
// items
if lSchema.Items.Value != nil && rSchema.Items.Value != nil {
if lSchema.Items.Value.IsA() && rSchema.Items.Value.IsA() {
if !low.AreEqual(lSchema.Items.Value.A, rSchema.Items.Value.A) {
sc.ItemsChanges = CompareSchemas(lSchema.Items.Value.A, rSchema.Items.Value.A)
}
} else {
CreateChange(changes, Modified, v3.ItemsLabel,
lSchema.Items.ValueNode, rSchema.Items.ValueNode, true, lSchema.Items.Value.B, rSchema.Items.Value.B)
}
}
// added Items
if lSchema.Items.Value == nil && rSchema.Items.Value != nil {
CreateChange(changes, ObjectAdded, v3.ItemsLabel,
nil, rSchema.Items.ValueNode, true, nil, rSchema.Items.Value)
}
// removed Items
if lSchema.Items.Value != nil && rSchema.Items.Value == nil {
CreateChange(changes, ObjectRemoved, v3.ItemsLabel,
lSchema.Items.ValueNode, nil, true, lSchema.Items.Value, nil)
}
// check extensions // check extensions
sc.ExtensionChanges = CompareExtensions(lSchema.Extensions, rSchema.Extensions) sc.ExtensionChanges = CompareExtensions(lSchema.Extensions, rSchema.Extensions)

View File

@@ -544,6 +544,473 @@ components:
assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property) assert.Equal(t, v3.PropertiesLabel, changes.Changes[0].Property)
} }
func TestCompareSchemas_PropertyNames(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
propertyNames:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
propertyNames:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, 1, changes.PropertyNamesChanges.PropertyChanges.TotalChanges())
}
func TestCompareSchemas_PropertyNames_Added(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
propertyNames:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.PropertyNamesLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_PropertyNames_Removed(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
propertyNames:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(rSchemaProxy, lSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.PropertyNamesLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_Contains(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
contains:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
contains:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, 1, changes.ContainsChanges.PropertyChanges.TotalChanges())
}
func TestCompareSchemas_Contains_Added(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
contains:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.ContainsLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_Contains_Removed(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
contains:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(rSchemaProxy, lSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.ContainsLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_UnevaluatedProperties(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
unevaluatedProperties:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
unevaluatedProperties:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, 1, changes.UnevaluatedPropertiesChanges.PropertyChanges.TotalChanges())
}
func TestCompareSchemas_UnevaluatedProperties_Added(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
unevaluatedProperties:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.UnevaluatedPropertiesLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_UnevaluatedProperties_Removed(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
unevaluatedProperties:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(rSchemaProxy, lSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.UnevaluatedPropertiesLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_UnevaluatedItems(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
unevaluatedItems:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
unevaluatedItems:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, 1, changes.UnevaluatedItemsChanges.PropertyChanges.TotalChanges())
}
func TestCompareSchemas_UnevaluatedItems_Added(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
unevaluatedItems:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.UnevaluatedItemsLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_UnevaluatedItems_Removed(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
unevaluatedItems:
type: int`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(rSchemaProxy, lSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.UnevaluatedItemsLabel, changes.Changes[0].Property)
}
func TestCompareSchemas_ItemsBoolean(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
items: true`
right := `openapi: 3.1
components:
schemas:
OK:
items: false`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
}
func TestCompareSchemas_ItemsAdded(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
items: true`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
}
func TestCompareSchemas_ItemsRemoved(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
items: true`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(rSchemaProxy, lSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
}
func TestCompareSchemas_NotAdded(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
not:
type: string`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(lSchemaProxy, rSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
}
func TestCompareSchemas_NotRemoved(t *testing.T) {
left := `openapi: 3.1
components:
schemas:
OK:
type: string`
right := `openapi: 3.1
components:
schemas:
OK:
type: string
not:
type: string`
leftDoc, rightDoc := test_BuildDoc(left, right)
// extract left reference schema and non reference schema.
lSchemaProxy := leftDoc.Components.Value.FindSchema("OK").Value
rSchemaProxy := rightDoc.Components.Value.FindSchema("OK").Value
changes := CompareSchemas(rSchemaProxy, lSchemaProxy)
assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges())
}
func TestCompareSchemas_PropertyChanged(t *testing.T) { func TestCompareSchemas_PropertyChanged(t *testing.T) {
left := `openapi: 3.0 left := `openapi: 3.0
components: components:
@@ -801,10 +1268,10 @@ components:
assert.NotNil(t, changes) assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.TypeLabel, changes.ItemsChanges[0].Changes[0].Property) assert.Equal(t, v3.TypeLabel, changes.ItemsChanges.Changes[0].Property)
assert.Equal(t, Modified, changes.ItemsChanges[0].Changes[0].ChangeType) assert.Equal(t, Modified, changes.ItemsChanges.Changes[0].ChangeType)
assert.Equal(t, "string", changes.ItemsChanges[0].Changes[0].New) assert.Equal(t, "string", changes.ItemsChanges.Changes[0].New)
assert.Equal(t, "bool", changes.ItemsChanges[0].Changes[0].Original) assert.Equal(t, "bool", changes.ItemsChanges.Changes[0].Original)
} }
func TestCompareSchemas_ItemsModifyAndAddItemArray(t *testing.T) { func TestCompareSchemas_ItemsModifyAndAddItemArray(t *testing.T) {
@@ -834,10 +1301,10 @@ components:
assert.NotNil(t, changes) assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.TypeLabel, changes.ItemsChanges[0].Changes[0].Property) assert.Equal(t, v3.TypeLabel, changes.ItemsChanges.Changes[0].Property)
assert.Equal(t, Modified, changes.ItemsChanges[0].Changes[0].ChangeType) assert.Equal(t, Modified, changes.ItemsChanges.Changes[0].ChangeType)
assert.Equal(t, "string", changes.ItemsChanges[0].Changes[0].New) assert.Equal(t, "string", changes.ItemsChanges.Changes[0].New)
assert.Equal(t, "bool", changes.ItemsChanges[0].Changes[0].Original) assert.Equal(t, "bool", changes.ItemsChanges.Changes[0].Original)
} }
func TestCompareSchemas_NotModifyAndAddItem(t *testing.T) { func TestCompareSchemas_NotModifyAndAddItem(t *testing.T) {
@@ -867,10 +1334,10 @@ components:
assert.NotNil(t, changes) assert.NotNil(t, changes)
assert.Equal(t, 1, changes.TotalChanges()) assert.Equal(t, 1, changes.TotalChanges())
assert.Equal(t, 1, changes.TotalBreakingChanges()) assert.Equal(t, 1, changes.TotalBreakingChanges())
assert.Equal(t, v3.TypeLabel, changes.NotChanges[0].Changes[0].Property) assert.Equal(t, v3.TypeLabel, changes.NotChanges.Changes[0].Property)
assert.Equal(t, Modified, changes.NotChanges[0].Changes[0].ChangeType) assert.Equal(t, Modified, changes.NotChanges.Changes[0].ChangeType)
assert.Equal(t, "string", changes.NotChanges[0].Changes[0].New) assert.Equal(t, "string", changes.NotChanges.Changes[0].New)
assert.Equal(t, "bool", changes.NotChanges[0].Changes[0].Original) assert.Equal(t, "bool", changes.NotChanges.Changes[0].Original)
} }
func TestCompareSchemas_DiscriminatorChange(t *testing.T) { func TestCompareSchemas_DiscriminatorChange(t *testing.T) {