From d51d2fcd270a012fe90bcb54543755dca752e13b Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Sat, 3 Dec 2022 14:08:14 -0500 Subject: [PATCH] Added `contains`, `minContains` and `maxContains` to schema #28 Added support for missing 3.1 schema properties, however it does not cover the `boolean` case --- datamodel/high/base/schema.go | 26 ++++++++++++++++++++++-- datamodel/high/base/schema_test.go | 11 +++++++++- datamodel/high/v3/document_test.go | 2 +- datamodel/low/base/constants.go | 1 + datamodel/low/base/schema.go | 32 ++++++++++++++++++++++++++++++ datamodel/low/base/schema_test.go | 12 ++++++++++- 6 files changed, 79 insertions(+), 5 deletions(-) diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index f87e3c5..85f6d69 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -55,9 +55,14 @@ type Schema struct { // in 3.1 prefixItems provides tuple validation support. PrefixItems []*SchemaProxy + // In 3.1 contains is used by arrays to define a single schema + Contains *SchemaProxy + MinContains *int64 + MaxContains *int64 + // Compatible with all versions Not []*SchemaProxy - Items []*SchemaProxy + Items *SchemaProxy Properties map[string]*SchemaProxy Title string MultipleOf *int64 @@ -139,6 +144,19 @@ func NewSchema(schema *base.Schema) *Schema { if !schema.MinProperties.IsEmpty() { s.MinProperties = &schema.MinProperties.Value } + if !schema.MaxContains.IsEmpty() { + s.MaxContains = &schema.MaxContains.Value + } + if !schema.MinContains.IsEmpty() { + s.MinContains = &schema.MinContains.Value + } + if !schema.Contains.IsEmpty() { + s.Contains = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{ + ValueNode: schema.Contains.ValueNode, + Value: schema.Contains.Value, + }} + } + s.Pattern = schema.Pattern.Value s.Format = schema.Format.Value @@ -197,6 +215,8 @@ func NewSchema(schema *base.Schema) *Schema { } s.Enum = enum + // build out + // async work. // any polymorphic properties need to be handled in their own threads // any properties each need to be processed in their own thread. @@ -312,7 +332,9 @@ func NewSchema(schema *base.Schema) *Schema { s.OneOf = oneOf s.AnyOf = anyOf s.AllOf = allOf - s.Items = items + if len(items) > 0 { + s.Items = items[0] + } s.PrefixItems = prefixItems s.Not = not return s diff --git a/datamodel/high/base/schema_test.go b/datamodel/high/base/schema_test.go index 85835ba..da8328f 100644 --- a/datamodel/high/base/schema_test.go +++ b/datamodel/high/base/schema_test.go @@ -191,7 +191,11 @@ minProperties: 1 nullable: true readOnly: true writeOnly: false -deprecated: true` +deprecated: true +contains: + type: int +minContains: 1 +maxContains: 10` var compNode yaml.Node _ = yaml.Unmarshal([]byte(testSpec), &compNode) @@ -213,6 +217,11 @@ deprecated: true` assert.NotNil(t, compiled) assert.Nil(t, schemaProxy.GetBuildError()) + // check contains + assert.Equal(t, "int", compiled.Contains.Schema().Type[0]) + assert.Equal(t, int64(10), *compiled.MaxContains) + assert.Equal(t, int64(1), *compiled.MinContains) + wentLow := compiled.GoLow() assert.Equal(t, 114, wentLow.AdditionalProperties.ValueNode.Line) diff --git a/datamodel/high/v3/document_test.go b/datamodel/high/v3/document_test.go index 145730c..a2d957b 100644 --- a/datamodel/high/v3/document_test.go +++ b/datamodel/high/v3/document_test.go @@ -203,7 +203,7 @@ func TestNewDocument_Components_Schemas(t *testing.T) { assert.Equal(t, 445, b.Schema().GoLow().FindProperty("name").ValueNode.Line) f := h.Components.Schemas["Fries"] - assert.Equal(t, "salt", f.Schema().Properties["seasoning"].Schema().Items[0].Schema().Example) + assert.Equal(t, "salt", f.Schema().Properties["seasoning"].Schema().Items.Schema().Example) assert.Len(t, f.Schema().Properties["favoriteDrink"].Schema().Properties["drinkType"].Schema().Enum, 2) d := h.Components.Schemas["Drink"] diff --git a/datamodel/low/base/constants.go b/datamodel/low/base/constants.go index e5ee478..3213605 100644 --- a/datamodel/low/base/constants.go +++ b/datamodel/low/base/constants.go @@ -18,6 +18,7 @@ const ( XMLLabel = "xml" ItemsLabel = "items" PrefixItemsLabel = "prefixItems" + ContainsLabel = "contains" AllOfLabel = "allOf" AnyOfLabel = "anyOf" OneOfLabel = "oneOf" diff --git a/datamodel/low/base/schema.go b/datamodel/low/base/schema.go index 9c3e8dd..8af3c2c 100644 --- a/datamodel/low/base/schema.go +++ b/datamodel/low/base/schema.go @@ -72,6 +72,10 @@ type Schema struct { Examples low.NodeReference[[]low.ValueReference[any]] // in 3.1 PrefixItems provides tuple validation using prefixItems. PrefixItems low.NodeReference[[]low.ValueReference[*SchemaProxy]] + // in 3.1 Contains is used by arrays and points to a Schema. + Contains low.NodeReference[*SchemaProxy] + MinContains low.NodeReference[int64] + MaxContains low.NodeReference[int64] // Compatible with all versions Title low.NodeReference[string] @@ -367,6 +371,17 @@ func (s *Schema) Hash() [32]byte { if s.Example.Value != nil { d = append(d, low.GenerateHashString(s.Example.Value)) } + + // contains + if !s.Contains.IsEmpty() { + d = append(d, low.GenerateHashString(s.Contains.Value)) + } + if !s.MinContains.IsEmpty() { + d = append(d, fmt.Sprint(s.MinContains.Value)) + } + if !s.MaxContains.IsEmpty() { + d = append(d, fmt.Sprint(s.MaxContains.Value)) + } if !s.Examples.IsEmpty() { var xph []string for w := range s.Examples.Value { @@ -632,6 +647,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { } var allOf, anyOf, oneOf, not, items, prefixItems []low.ValueReference[*SchemaProxy] + var contains low.ValueReference[*SchemaProxy] _, allOfLabel, allOfValue := utils.FindKeyNodeFullTop(AllOfLabel, root.Content) _, anyOfLabel, anyOfValue := utils.FindKeyNodeFullTop(AnyOfLabel, root.Content) @@ -639,6 +655,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { _, notLabel, notValue := utils.FindKeyNodeFullTop(NotLabel, root.Content) _, itemsLabel, itemsValue := utils.FindKeyNodeFullTop(ItemsLabel, root.Content) _, prefixItemsLabel, prefixItemsValue := utils.FindKeyNodeFullTop(PrefixItemsLabel, root.Content) + _, containsLabel, containsValue := utils.FindKeyNodeFullTop(ContainsLabel, root.Content) errorChan := make(chan error) allOfChan := make(chan schemaProxyBuildResult) @@ -647,6 +664,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { itemsChan := make(chan schemaProxyBuildResult) prefixItemsChan := make(chan schemaProxyBuildResult) notChan := make(chan schemaProxyBuildResult) + containsChan := make(chan schemaProxyBuildResult) totalBuilds := countSubSchemaItems(allOfValue) + countSubSchemaItems(anyOfValue) + @@ -673,6 +691,10 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { if notValue != nil { go buildSchema(notChan, notLabel, notValue, errorChan, idx) } + if containsValue != nil { + totalBuilds++ + go buildSchema(containsChan, containsLabel, containsValue, errorChan, idx) + } completeCount := 0 for completeCount < totalBuilds { @@ -697,6 +719,9 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { case r := <-notChan: completeCount++ not = append(not, r.v) + case r := <-containsChan: + completeCount++ + contains = r.v } } @@ -743,6 +768,13 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { ValueNode: prefixItemsValue, } } + if !contains.IsEmpty() { + s.Contains = low.NodeReference[*SchemaProxy]{ + Value: contains.Value, + KeyNode: containsLabel, + ValueNode: containsValue, + } + } return nil } diff --git a/datamodel/low/base/schema_test.go b/datamodel/low/base/schema_test.go index c46142f..0b0452c 100644 --- a/datamodel/low/base/schema_test.go +++ b/datamodel/low/base/schema_test.go @@ -126,7 +126,11 @@ enum: x-pizza: tasty examples: - hey - - hi!` + - hi! +contains: + type: int +maxContains: 10 +minContains: 1` } func Test_Schema(t *testing.T) { @@ -271,6 +275,12 @@ func Test_Schema(t *testing.T) { assert.Equal(t, "cat", mv.Value) mv = sch.Discriminator.Value.FindMappingValue("pizza") assert.Equal(t, "party", mv.Value) + + // check contains + assert.Equal(t, "int", sch.Contains.Value.Schema().Type.Value.A) + assert.Equal(t, int64(1), sch.MinContains.Value) + assert.Equal(t, int64(10), sch.MaxContains.Value) + } func TestSchema_Hash(t *testing.T) {