diff --git a/datamodel/high/base/discriminator.go b/datamodel/high/base/discriminator.go index f761803..06c3d4b 100644 --- a/datamodel/high/base/discriminator.go +++ b/datamodel/high/base/discriminator.go @@ -6,6 +6,7 @@ package base import ( low2 "github.com/pb33f/libopenapi/datamodel/high" low "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -19,8 +20,8 @@ import ( // // v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object type Discriminator struct { - PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"` - Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` + PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"` + Mapping orderedmap.Map[string, string] `json:"mapping,omitempty" yaml:"mapping,omitempty"` low *low.Discriminator } @@ -29,9 +30,9 @@ func NewDiscriminator(disc *low.Discriminator) *Discriminator { d := new(Discriminator) d.low = disc d.PropertyName = disc.PropertyName.Value - mapping := make(map[string]string) - for k, v := range disc.Mapping.Value { - mapping[k.Value] = v.Value + mapping := orderedmap.New[string, string]() + for pair := disc.Mapping.Value.First(); pair != nil; pair = pair.Next() { + mapping.Set(pair.Key().Value, pair.Value().Value) } d.Mapping = mapping return d diff --git a/datamodel/high/base/discriminator_test.go b/datamodel/high/base/discriminator_test.go index 85dde25..80485e4 100644 --- a/datamodel/high/base/discriminator_test.go +++ b/datamodel/high/base/discriminator_test.go @@ -5,16 +5,16 @@ package base import ( "fmt" + "strings" + "testing" + lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowbase "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "strings" - "testing" ) func TestNewDiscriminator(t *testing.T) { - var cNode yaml.Node yml := `propertyName: coffee @@ -31,17 +31,15 @@ mapping: highDiscriminator := NewDiscriminator(&lowDiscriminator) assert.Equal(t, "coffee", highDiscriminator.PropertyName) - assert.Equal(t, "in the morning", highDiscriminator.Mapping["fogCleaner"]) + assert.Equal(t, "in the morning", highDiscriminator.Mapping.GetOrZero("fogCleaner")) assert.Equal(t, 3, highDiscriminator.GoLow().FindMappingValue("fogCleaner").ValueNode.Line) // render the example as YAML rendered, _ := highDiscriminator.Render() assert.Equal(t, strings.TrimSpace(string(rendered)), yml) - } func ExampleNewDiscriminator() { - // create a yaml representation of a discriminator (can be JSON, doesn't matter) yml := `propertyName: coffee mapping: @@ -59,6 +57,6 @@ mapping: highDiscriminator := NewDiscriminator(&lowDiscriminator) // print out a mapping defined for the discriminator. - fmt.Print(highDiscriminator.Mapping["coffee"]) + fmt.Print(highDiscriminator.Mapping.GetOrZero("coffee")) // Output: in the morning } diff --git a/datamodel/high/base/dynamic_value_test.go b/datamodel/high/base/dynamic_value_test.go index 799be06..f2cad77 100644 --- a/datamodel/high/base/dynamic_value_test.go +++ b/datamodel/high/base/dynamic_value_test.go @@ -5,13 +5,14 @@ package base import ( "context" + "strings" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" lowbase "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "strings" - "testing" ) func TestDynamicValue_Render_A(t *testing.T) { @@ -57,7 +58,6 @@ func TestDynamicValue_Render_Float64(t *testing.T) { } func TestDynamicValue_Render_Ptr(t *testing.T) { - type cake struct { Cake string } @@ -68,7 +68,6 @@ func TestDynamicValue_Render_Ptr(t *testing.T) { } func TestDynamicValue_Render_PtrRenderable(t *testing.T) { - tag := &Tag{ Name: "cake", } @@ -79,7 +78,6 @@ func TestDynamicValue_Render_PtrRenderable(t *testing.T) { } func TestDynamicValue_RenderInline(t *testing.T) { - tag := &Tag{ Name: "cake", } @@ -90,7 +88,6 @@ func TestDynamicValue_RenderInline(t *testing.T) { } func TestDynamicValue_MarshalYAMLInline(t *testing.T) { - const ymlComponents = `components: schemas: rice: @@ -130,11 +127,10 @@ func TestDynamicValue_MarshalYAMLInline(t *testing.T) { // convert node into yaml bits, _ := yaml.Marshal(rend) - assert.Equal(t, "properties:\n rice:\n type: array\n items:\n type: string", strings.TrimSpace(string(bits))) + assert.Equal(t, "properties:\n rice:\n $ref: '#/components/schemas/rice'", strings.TrimSpace(string(bits))) } func TestDynamicValue_MarshalYAMLInline_Error(t *testing.T) { - const ymlComponents = `components: schemas: rice: diff --git a/datamodel/high/v3/document_test.go b/datamodel/high/v3/document_test.go index 6113faf..6f5dc3e 100644 --- a/datamodel/high/v3/document_test.go +++ b/datamodel/high/v3/document_test.go @@ -231,7 +231,7 @@ func TestNewDocument_Components_Schemas(t *testing.T) { assert.Len(t, d.Schema().Required, 2) assert.True(t, d.Schema().AdditionalProperties.B) assert.Equal(t, "drinkType", d.Schema().Discriminator.PropertyName) - assert.Equal(t, "some value", d.Schema().Discriminator.Mapping["drink"]) + assert.Equal(t, "some value", d.Schema().Discriminator.Mapping.GetOrZero("drink")) assert.Equal(t, 516, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Line) assert.Equal(t, 23, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Column) @@ -300,8 +300,8 @@ func TestNewDocument_Components_SecuritySchemes(t *testing.T) { assert.Equal(t, "an oAuth security scheme", oAuth.Description) assert.Equal(t, 375, oAuth.GoLow().Description.ValueNode.Line) assert.Equal(t, 20, oAuth.GoLow().Description.ValueNode.Column) - assert.Equal(t, 2, len(oAuth.Flows.Implicit.Scopes)) - assert.Equal(t, "read all burgers", oAuth.Flows.Implicit.Scopes["read:burgers"]) + assert.Equal(t, 2, oAuth.Flows.Implicit.Scopes.Len()) + assert.Equal(t, "read all burgers", oAuth.Flows.Implicit.Scopes.GetOrZero("read:burgers")) assert.Equal(t, "https://pb33f.io/oauth", oAuth.Flows.AuthorizationCode.AuthorizationUrl) // check the lowness is low. @@ -553,7 +553,7 @@ func TestCircularReferencesDoc(t *testing.T) { lDoc, err := lowv3.CreateDocumentFromConfig(info, datamodel.NewDocumentConfiguration()) assert.Len(t, utils.UnwrapErrors(err), 3) d := NewDocument(lDoc) - assert.Len(t, d.Components.Schemas, 9) + assert.Equal(t, 9, d.Components.Schemas.Len()) assert.Len(t, d.Index.GetCircularReferences(), 3) } diff --git a/datamodel/high/v3/oauth_flow.go b/datamodel/high/v3/oauth_flow.go index 3e77405..432700c 100644 --- a/datamodel/high/v3/oauth_flow.go +++ b/datamodel/high/v3/oauth_flow.go @@ -6,6 +6,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/high" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -15,8 +16,7 @@ type OAuthFlow struct { AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` RefreshUrl string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` - // FIXME: Scopes must be converted to orderedmap.Map. Fix unmarshaling issue causing omitted `scopes` stanza on parsing. - Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"` + Scopes orderedmap.Map[string, string] `json:"scopes,omitempty" yaml:"scopes,omitempty"` Extensions map[string]any `json:"-" yaml:"-"` low *low.OAuthFlow } @@ -28,9 +28,9 @@ func NewOAuthFlow(flow *low.OAuthFlow) *OAuthFlow { o.TokenUrl = flow.TokenUrl.Value o.AuthorizationUrl = flow.AuthorizationUrl.Value o.RefreshUrl = flow.RefreshUrl.Value - scopes := map[string]string{} - for k, v := range flow.Scopes.Value { - scopes[k.Value] = v.Value + scopes := orderedmap.New[string, string]() + for pair := flow.Scopes.Value.First(); pair != nil; pair = pair.Next() { + scopes.Set(pair.Key().Value, pair.Value().Value) } o.Scopes = scopes o.Extensions = high.ExtractExtensions(flow.Extensions) diff --git a/datamodel/high/v3/oauth_flow_test.go b/datamodel/high/v3/oauth_flow_test.go index e63761c..36ed971 100644 --- a/datamodel/high/v3/oauth_flow_test.go +++ b/datamodel/high/v3/oauth_flow_test.go @@ -7,19 +7,20 @@ import ( "strings" "testing" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" ) func TestOAuthFlow_MarshalYAML(t *testing.T) { + scopes := orderedmap.New[string, string]() + scopes.Set("chicken", "nuggets") + scopes.Set("beefy", "soup") oflow := &OAuthFlow{ AuthorizationUrl: "https://pb33f.io", TokenUrl: "https://pb33f.io/token", RefreshUrl: "https://pb33f.io/refresh", - Scopes: map[string]string{ - "chicken": "nuggets", - "beefy": "soup", - }, + Scopes: scopes, } rend, _ := oflow.Render() @@ -45,5 +46,4 @@ x-burgers: why not?` rend, _ = oflow.Render() assert.Equal(t, desired, strings.TrimSpace(string(rend))) - } diff --git a/datamodel/high/v3/oauth_flows_test.go b/datamodel/high/v3/oauth_flows_test.go index 94e8369..447333b 100644 --- a/datamodel/high/v3/oauth_flows_test.go +++ b/datamodel/high/v3/oauth_flows_test.go @@ -16,7 +16,6 @@ import ( ) func TestNewOAuthFlows(t *testing.T) { - yml := `implicit: authorizationUrl: https://pb33f.io/oauth/implicit scopes: @@ -49,10 +48,10 @@ clientCredentials: r := NewOAuthFlows(&n) - assert.Len(t, r.Implicit.Scopes, 2) - assert.Len(t, r.AuthorizationCode.Scopes, 2) - assert.Len(t, r.Password.Scopes, 2) - assert.Len(t, r.ClientCredentials.Scopes, 2) + assert.Equal(t, 2, r.Implicit.Scopes.Len()) + assert.Equal(t, 2, r.AuthorizationCode.Scopes.Len()) + assert.Equal(t, 2, r.Password.Scopes.Len()) + assert.Equal(t, 2, r.ClientCredentials.Scopes.Len()) assert.Equal(t, 2, r.GoLow().Implicit.Value.AuthorizationUrl.KeyNode.Line) // now render it back out, and it should be identical! @@ -83,8 +82,7 @@ clientCredentials: CHIP:CHOP: microwave a sock` // now modify it and render it back out, and it should be identical! - r.ClientCredentials.Scopes["CHIP:CHOP"] = "microwave a sock" + r.ClientCredentials.Scopes.Set("CHIP:CHOP", "microwave a sock") rBytes, _ = r.Render() assert.Equal(t, modified, strings.TrimSpace(string(rBytes))) - } diff --git a/datamodel/low/base/discriminator.go b/datamodel/low/base/discriminator.go index 6f94a3f..b83caad 100644 --- a/datamodel/low/base/discriminator.go +++ b/datamodel/low/base/discriminator.go @@ -5,9 +5,10 @@ package base import ( "crypto/sha256" - "github.com/pb33f/libopenapi/datamodel/low" - "sort" "strings" + + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/orderedmap" ) // Discriminator is only used by OpenAPI 3+ documents, it represents a polymorphic discriminator used for schemas @@ -21,14 +22,15 @@ import ( // v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object type Discriminator struct { PropertyName low.NodeReference[string] - Mapping low.NodeReference[map[low.KeyReference[string]]low.ValueReference[string]] + Mapping low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]] low.Reference } // FindMappingValue will return a ValueReference containing the string mapping value func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string] { - for k, v := range d.Mapping.Value { - if k.Value == key { + for pair := d.Mapping.Value.First(); pair != nil; pair = pair.Next() { + if pair.Key().Value == key { + v := pair.Value() return &v } } @@ -37,20 +39,15 @@ func (d *Discriminator) FindMappingValue(key string) *low.ValueReference[string] // Hash will return a consistent SHA256 Hash of the Discriminator object func (d *Discriminator) Hash() [32]byte { - // calculate a hash from every property. var f []string if d.PropertyName.Value != "" { f = append(f, d.PropertyName.Value) } - propertyKeys := make([]string, 0, len(d.Mapping.Value)) - for i := range d.Mapping.Value { - propertyKeys = append(propertyKeys, i.Value) - } - sort.Strings(propertyKeys) - for k := range propertyKeys { - prop := d.FindMappingValue(propertyKeys[k]) - f = append(f, prop.Value) + + for pair := orderedmap.First(d.Mapping.Value); pair != nil; pair = pair.Next() { + f = append(f, pair.Value().Value) } + return sha256.Sum256([]byte(strings.Join(f, "|"))) } diff --git a/datamodel/low/base/schema.go b/datamodel/low/base/schema.go index 418d78e..6c464d7 100644 --- a/datamodel/low/base/schema.go +++ b/datamodel/low/base/schema.go @@ -549,7 +549,6 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde // determine exclusive minimum type, bool (3.0) or int (3.1) _, exMinLabel, exMinValue := utils.FindKeyNodeFullTop(ExclusiveMinimumLabel, root.Content) if exMinValue != nil { - // if there is an index, determine if this a 3.0 or 3.1 schema if idx != nil { if idx.GetConfig().SpecInfo.VersionNumeric == 3.1 { @@ -593,7 +592,6 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde // determine exclusive maximum type, bool (3.0) or int (3.1) _, exMaxLabel, exMaxValue := utils.FindKeyNodeFullTop(ExclusiveMaximumLabel, root.Content) if exMaxValue != nil { - // if there is an index, determine if this a 3.0 or 3.1 schema if idx != nil { if idx.GetConfig().SpecInfo.VersionNumeric == 3.1 { @@ -1038,62 +1036,40 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde } func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) { - // for property, build in a new thread! - bChan := make(chan schemaProxyBuildResult) - - buildProperty := func(ctx context.Context, 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{ctx: ctx, kn: label, vn: value, idx: idx, isReference: isRef, referenceLookup: refString}, - ValueNode: value, - }, - } - } - _, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content) if propsNode != nil { propertyMap := orderedmap.New[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 } - foundCtx := ctx // check our prop isn't reference isRef := false refString := "" if h, _, l := utils.IsNodeRefValue(prop); h { - ref, _, _, fctx := low.LocateRefNodeWithContext(ctx, prop, idx) + ref, _, _, _ := low.LocateRefNodeWithContext(ctx, prop, idx) if ref != nil { isRef = true prop = ref refString = l - foundCtx = fctx } else { return nil, fmt.Errorf("schema properties build failed: cannot find reference %s, line %d, col %d", prop.Content[1].Value, prop.Content[1].Line, prop.Content[1].Column) } } - totalProps++ - go buildProperty(foundCtx, currentProp, prop, bChan, isRef, refString) - } - completedProps := 0 - for completedProps < totalProps { - select { - case res := <-bChan: - completedProps++ - propertyMap.Set(res.k, res.v) - } + + propertyMap.Set(low.KeyReference[string]{ + KeyNode: currentProp, + Value: currentProp.Value, + }, low.ValueReference[*SchemaProxy]{ + Value: &SchemaProxy{ctx: ctx, kn: currentProp, vn: prop, idx: idx, isReference: isRef, referenceLookup: refString}, + ValueNode: prop, + }) } + return &low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]{ Value: propertyMap, KeyNode: propLabel, @@ -1280,7 +1256,7 @@ func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) ( schNode = ref if foundIdx != nil { // TODO: check on this - //idx = foundIdx + // idx = foundIdx } ctx = nCtx } else { diff --git a/datamodel/low/model_builder.go b/datamodel/low/model_builder.go index 321145b..8e55411 100644 --- a/datamodel/low/model_builder.go +++ b/datamodel/low/model_builder.go @@ -10,6 +10,7 @@ import ( "strings" "sync" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -389,25 +390,25 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er field.Set(reflect.ValueOf(ref)) } } - case reflect.TypeOf(NodeReference[map[KeyReference[string]]ValueReference[string]]{}): + case reflect.TypeOf(NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]]{}): if utils.IsNodeMap(valueNode) { if field.CanSet() { - items := make(map[KeyReference[string]]ValueReference[string]) + items := orderedmap.New[KeyReference[string], ValueReference[string]]() var cf *yaml.Node for i, sliceItem := range valueNode.Content { if i%2 == 0 { cf = sliceItem continue } - items[KeyReference[string]{ + items.Set(KeyReference[string]{ Value: cf.Value, KeyNode: cf, - }] = ValueReference[string]{ + }, ValueReference[string]{ Value: sliceItem.Value, ValueNode: sliceItem, - } + }) } - ref := NodeReference[map[KeyReference[string]]ValueReference[string]]{ + ref := NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]]{ Value: items, KeyNode: keyNode, ValueNode: valueNode, diff --git a/datamodel/low/model_builder_test.go b/datamodel/low/model_builder_test.go index 2dbeac9..c5e16f4 100644 --- a/datamodel/low/model_builder_test.go +++ b/datamodel/low/model_builder_test.go @@ -1,10 +1,12 @@ package low import ( - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" "sync" "testing" + + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) type hotdog struct { @@ -27,11 +29,10 @@ type hotdog struct { LotsOfUnknowns []NodeReference[any] Where map[string]NodeReference[any] There map[string]NodeReference[string] - AllTheThings NodeReference[map[KeyReference[string]]ValueReference[string]] + AllTheThings NodeReference[orderedmap.Map[KeyReference[string], ValueReference[string]]] } func TestBuildModel_Mismatch(t *testing.T) { - yml := `crisps: are tasty` var rootNode yaml.Node @@ -42,11 +43,9 @@ func TestBuildModel_Mismatch(t *testing.T) { cErr := BuildModel(&rootNode, &hd) assert.NoError(t, cErr) assert.Empty(t, hd.Name) - } func TestBuildModel(t *testing.T) { - yml := `name: yummy valueName: yammy beef: true @@ -135,19 +134,18 @@ allTheThings: assert.Equal(t, 324938249028.98234892374892374923874823974, hd.Mustard.Value) allTheThings := hd.AllTheThings.Value - for i := range allTheThings { - if i.Value == "beer" { - assert.Equal(t, "isGood", allTheThings[i].Value) + for pair := allTheThings.First(); pair != nil; pair = pair.Next() { + if pair.Key().Value == "beer" { + assert.Equal(t, "isGood", pair.Value().Value) } - if i.Value == "cake" { - assert.Equal(t, "isNice", allTheThings[i].Value) + if pair.Key().Value == "cake" { + assert.Equal(t, "isNice", pair.Value().Value) } } assert.NoError(t, cErr) } func TestBuildModel_UseCopyNotRef(t *testing.T) { - yml := `cake: -99999` var rootNode yaml.Node @@ -158,11 +156,9 @@ func TestBuildModel_UseCopyNotRef(t *testing.T) { cErr := BuildModel(&rootNode, hd) assert.Error(t, cErr) assert.Empty(t, hd.Name) - } func TestBuildModel_UseUnsupportedPrimitive(t *testing.T) { - type notSupported struct { cake string } @@ -176,11 +172,9 @@ func TestBuildModel_UseUnsupportedPrimitive(t *testing.T) { cErr := BuildModel(rootNode.Content[0], &ns) assert.Error(t, cErr) assert.Empty(t, ns.cake) - } func TestBuildModel_UsingInternalConstructs(t *testing.T) { - type internal struct { Extensions NodeReference[string] PathItems NodeReference[string] @@ -208,7 +202,6 @@ thing: yeah` } func TestSetField_NodeRefAny_Error(t *testing.T) { - type internal struct { Thing []NodeReference[any] } @@ -224,11 +217,9 @@ func TestSetField_NodeRefAny_Error(t *testing.T) { try := BuildModel(rootNode.Content[0], ins) assert.Error(t, try) - } func TestSetField_MapHelperWrapped(t *testing.T) { - type internal struct { Thing KeyReference[map[KeyReference[string]]ValueReference[string]] } @@ -249,7 +240,6 @@ func TestSetField_MapHelperWrapped(t *testing.T) { } func TestSetField_MapHelper(t *testing.T) { - type internal struct { Thing map[KeyReference[string]]ValueReference[string] } @@ -270,7 +260,6 @@ func TestSetField_MapHelper(t *testing.T) { } func TestSetField_ArrayHelper(t *testing.T) { - type internal struct { Thing NodeReference[[]ValueReference[string]] } @@ -291,7 +280,6 @@ func TestSetField_ArrayHelper(t *testing.T) { } func TestSetField_Enum_Helper(t *testing.T) { - type internal struct { Thing NodeReference[[]ValueReference[any]] } @@ -312,7 +300,6 @@ func TestSetField_Enum_Helper(t *testing.T) { } func TestSetField_Default_Helper(t *testing.T) { - type cake struct { thing int } @@ -336,7 +323,6 @@ func TestSetField_Default_Helper(t *testing.T) { } func TestHandleSlicesOfInts(t *testing.T) { - type internal struct { Thing NodeReference[[]ValueReference[any]] } @@ -377,7 +363,6 @@ func TestHandleSlicesOfBools(t *testing.T) { } func TestSetField_Ignore(t *testing.T) { - type Complex struct { name string } @@ -401,7 +386,6 @@ func TestSetField_Ignore(t *testing.T) { } func TestBuildModelAsync(t *testing.T) { - type internal struct { Thing KeyReference[map[KeyReference[string]]ValueReference[string]] } @@ -422,11 +406,9 @@ func TestBuildModelAsync(t *testing.T) { BuildModelAsync(rootNode.Content[0], ins, &wg, &errors) wg.Wait() assert.Len(t, ins.Thing.Value, 3) - } func TestBuildModelAsync_Error(t *testing.T) { - type internal struct { Thing []NodeReference[any] } @@ -447,5 +429,4 @@ func TestBuildModelAsync_Error(t *testing.T) { wg.Wait() assert.Len(t, errors, 1) assert.Len(t, ins.Thing, 0) - } diff --git a/datamodel/low/v3/create_document_test.go b/datamodel/low/v3/create_document_test.go index 47d74cb..10493dd 100644 --- a/datamodel/low/v3/create_document_test.go +++ b/datamodel/low/v3/create_document_test.go @@ -2,14 +2,15 @@ package v3 import ( "fmt" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" "log/slog" "net/http" "net/url" "os" "testing" + "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/utils" + "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" @@ -525,7 +526,7 @@ func TestCreateDocument_Components_Schemas(t *testing.T) { components := doc.Components.Value assert.NotNil(t, components) - assert.Len(t, components.Schemas.Value, 6) + assert.Equal(t, 6, components.Schemas.Value.Len()) burger := components.FindSchema("Burger").Value assert.NotNil(t, burger) @@ -539,7 +540,7 @@ func TestCreateDocument_Components_Schemas(t *testing.T) { fries := components.FindSchema("Fries") assert.NotNil(t, fries.Value) - assert.Len(t, fries.Value.Schema().Properties.Value, 3) + assert.Equal(t, 3, fries.Value.Schema().Properties.Value.Len()) p := fries.Value.Schema().FindProperty("favoriteDrink") assert.Equal(t, "a frosty cold beverage can be coke or sprite", p.Value.Schema().Description.Value) @@ -549,7 +550,7 @@ func TestCreateDocument_Components_SecuritySchemes(t *testing.T) { initTest() components := doc.Components.Value securitySchemes := components.SecuritySchemes.Value - assert.Len(t, securitySchemes, 3) + assert.Equal(t, 3, securitySchemes.Len()) apiKey := components.FindSecurityScheme("APIKeyScheme").Value assert.NotNil(t, apiKey) @@ -577,19 +578,19 @@ func TestCreateDocument_Components_Responses(t *testing.T) { initTest() components := doc.Components.Value responses := components.Responses.Value - assert.Len(t, responses, 1) + assert.Equal(t, 1, responses.Len()) dressingResponse := components.FindResponse("DressingResponse") assert.NotNil(t, dressingResponse.Value) assert.Equal(t, "all the dressings for a burger.", dressingResponse.Value.Description.Value) - assert.Len(t, dressingResponse.Value.Content.Value, 1) + assert.Equal(t, 1, dressingResponse.Value.Content.Value.Len()) } func TestCreateDocument_Components_Examples(t *testing.T) { initTest() components := doc.Components.Value examples := components.Examples.Value - assert.Len(t, examples, 1) + assert.Equal(t, 1, examples.Len()) quarterPounder := components.FindExample("QuarterPounder") assert.NotNil(t, quarterPounder.Value) @@ -601,19 +602,19 @@ func TestCreateDocument_Components_RequestBodies(t *testing.T) { initTest() components := doc.Components.Value requestBodies := components.RequestBodies.Value - assert.Len(t, requestBodies, 1) + assert.Equal(t, 1, requestBodies.Len()) burgerRequest := components.FindRequestBody("BurgerRequest") assert.NotNil(t, burgerRequest.Value) assert.Equal(t, "Give us the new burger!", burgerRequest.Value.Description.Value) - assert.Len(t, burgerRequest.Value.Content.Value, 1) + assert.Equal(t, 1, burgerRequest.Value.Content.Value.Len()) } func TestCreateDocument_Components_Headers(t *testing.T) { initTest() components := doc.Components.Value headers := components.Headers.Value - assert.Len(t, headers, 1) + assert.Equal(t, 1, headers.Len()) useOil := components.FindHeader("UseOil") assert.NotNil(t, useOil.Value) @@ -625,7 +626,7 @@ func TestCreateDocument_Components_Links(t *testing.T) { initTest() components := doc.Components.Value links := components.Links.Value - assert.Len(t, links, 2) + assert.Equal(t, 2, links.Len()) locateBurger := components.FindLink("LocateBurger") assert.NotNil(t, locateBurger.Value) @@ -646,11 +647,11 @@ func TestCreateDocument_Doc_Security(t *testing.T) { func TestCreateDocument_Callbacks(t *testing.T) { initTest() callbacks := doc.Components.Value.Callbacks.Value - assert.Len(t, callbacks, 1) + assert.Equal(t, 1, callbacks.Len()) bCallback := doc.Components.Value.FindCallback("BurgerCallback") assert.NotNil(t, bCallback.Value) - assert.Len(t, callbacks, 1) + assert.Equal(t, 1, callbacks.Len()) exp := bCallback.Value.FindExpression("{$request.query.queryUrl}") assert.NotNil(t, exp.Value) @@ -791,7 +792,6 @@ func TestCreateDocument_YamlAnchor(t *testing.T) { // build low-level document model document, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{}) - if err != nil { fmt.Printf("error: %s\n", err.Error()) panic("cannot build document") @@ -851,7 +851,6 @@ func ExampleCreateDocument() { // build low-level document model document, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{}) - if err != nil { fmt.Printf("error: %s\n", err.Error()) panic("cannot build document") diff --git a/datamodel/low/v3/header_test.go b/datamodel/low/v3/header_test.go index a80dc39..330a466 100644 --- a/datamodel/low/v3/header_test.go +++ b/datamodel/low/v3/header_test.go @@ -5,12 +5,14 @@ package v3 import ( "context" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "testing" ) func TestHeader_Build(t *testing.T) { @@ -168,7 +170,6 @@ func TestHeader_Build_Fail_Content(t *testing.T) { } func TestEncoding_Hash_n_Grab(t *testing.T) { - yml := `description: heady required: true deprecated: true @@ -241,7 +242,6 @@ schema: sch := n.GetSchema().Value.(*base.SchemaProxy).Schema() assert.Len(t, sch.Type.Value.B, 2) // using multiple types for 3.1 testing. assert.Equal(t, "what a good puppy", n.GetExample().Value) - assert.Len(t, n.GetExamples().Value, 1) - assert.Len(t, n.GetContent().Value.(map[low.KeyReference[string]]low.ValueReference[*MediaType]), 1) - + assert.Equal(t, 1, orderedmap.Cast[low.KeyReference[string], low.ValueReference[*base.Example]](n.GetExamples().Value).Len()) + assert.Equal(t, 1, orderedmap.Cast[low.KeyReference[string], low.ValueReference[*MediaType]](n.GetContent().Value).Len()) } diff --git a/datamodel/low/v3/media_type_test.go b/datamodel/low/v3/media_type_test.go index f8bc87f..51ef702 100644 --- a/datamodel/low/v3/media_type_test.go +++ b/datamodel/low/v3/media_type_test.go @@ -5,11 +5,12 @@ package v3 import ( "context" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "testing" ) func TestMediaType_Build(t *testing.T) { @@ -42,7 +43,7 @@ x-rock: and roll` assert.Equal(t, "why?", n.FindExample("what").Value.Value.Value) assert.Equal(t, "there?", n.FindExample("where").Value.Value.Value) assert.True(t, n.FindPropertyEncoding("chicken").Value.Explode.Value) - assert.Len(t, n.GetAllExamples(), 2) + assert.Equal(t, n.GetAllExamples().Len(), 2) } func TestMediaType_Build_Fail_Schema(t *testing.T) { @@ -76,7 +77,6 @@ func TestMediaType_Build_Fail_Examples(t *testing.T) { err = n.Build(context.Background(), nil, idxNode.Content[0], idx) assert.Error(t, err) - } func TestMediaType_Build_Fail_Encoding(t *testing.T) { @@ -97,7 +97,6 @@ func TestMediaType_Build_Fail_Encoding(t *testing.T) { } func TestMediaType_Hash(t *testing.T) { - yml := `schema: type: string example: a thing diff --git a/datamodel/low/v3/oauth_flows.go b/datamodel/low/v3/oauth_flows.go index 3f9a4ae..209d9f5 100644 --- a/datamodel/low/v3/oauth_flows.go +++ b/datamodel/low/v3/oauth_flows.go @@ -12,6 +12,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -97,7 +98,7 @@ type OAuthFlow struct { AuthorizationUrl low.NodeReference[string] TokenUrl low.NodeReference[string] RefreshUrl low.NodeReference[string] - Scopes low.NodeReference[map[low.KeyReference[string]]low.ValueReference[string]] + Scopes low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference } @@ -109,7 +110,7 @@ func (o *OAuthFlow) GetExtensions() map[low.KeyReference[string]]low.ValueRefere // FindScope attempts to locate a scope using a specified name. func (o *OAuthFlow) FindScope(scope string) *low.ValueReference[string] { - return low.FindItemInMap[string](scope, o.Scopes.Value) + return low.FindItemInOrderedMap[string](scope, o.Scopes.Value) } // FindExtension attempts to locate an extension with a specified key @@ -136,10 +137,10 @@ func (o *OAuthFlow) Hash() [32]byte { if !o.RefreshUrl.IsEmpty() { f = append(f, o.RefreshUrl.Value) } - keys := make([]string, len(o.Scopes.Value)) + keys := make([]string, orderedmap.Len(o.Scopes.Value)) z := 0 - for k, v := range o.Scopes.Value { - keys[z] = fmt.Sprintf("%s-%s", k.Value, sha256.Sum256([]byte(fmt.Sprint(v.Value)))) + for pair := orderedmap.First(o.Scopes.Value); pair != nil; pair = pair.Next() { + keys[z] = fmt.Sprintf("%s-%s", pair.Key().Value, sha256.Sum256([]byte(fmt.Sprint(pair.Value().Value)))) z++ } sort.Strings(keys) diff --git a/datamodel/low/v3/operation_test.go b/datamodel/low/v3/operation_test.go index 18a4f88..2caf0d0 100644 --- a/datamodel/low/v3/operation_test.go +++ b/datamodel/low/v3/operation_test.go @@ -5,16 +5,16 @@ package v3 import ( "context" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "testing" ) func TestOperation_Build(t *testing.T) { - yml := `tags: - meddy - maddy @@ -61,9 +61,9 @@ servers: assert.Equal(t, "beefyBeef", n.OperationId.Value) assert.Len(t, n.Parameters.Value, 2) assert.Equal(t, "a requestBody", n.RequestBody.Value.Description.Value) - assert.Len(t, n.Responses.Value.Codes, 1) + assert.Equal(t, 1, n.Responses.Value.Codes.Len()) assert.Equal(t, "an OK response", n.Responses.Value.FindResponseByCode("200").Value.Description.Value) - assert.Len(t, n.Callbacks.Value, 1) + assert.Equal(t, 1, n.Callbacks.Value.Len()) assert.Equal(t, "a nice callback", n.FindCallback("niceCallback").Value.FindExpression("ohISee").Value.Description.Value) assert.True(t, n.Deprecated.Value) @@ -76,7 +76,6 @@ servers: } func TestOperation_Build_FailDocs(t *testing.T) { - yml := `externalDocs: $ref: #borked` @@ -93,7 +92,6 @@ func TestOperation_Build_FailDocs(t *testing.T) { } func TestOperation_Build_FailParams(t *testing.T) { - yml := `parameters: $ref: #borked` @@ -110,7 +108,6 @@ func TestOperation_Build_FailParams(t *testing.T) { } func TestOperation_Build_FailRequestBody(t *testing.T) { - yml := `requestBody: $ref: #borked` @@ -127,7 +124,6 @@ func TestOperation_Build_FailRequestBody(t *testing.T) { } func TestOperation_Build_FailResponses(t *testing.T) { - yml := `responses: $ref: #borked` @@ -144,7 +140,6 @@ func TestOperation_Build_FailResponses(t *testing.T) { } func TestOperation_Build_FailCallbacks(t *testing.T) { - yml := `callbacks: $ref: #borked` @@ -161,7 +156,6 @@ func TestOperation_Build_FailCallbacks(t *testing.T) { } func TestOperation_Build_FailSecurity(t *testing.T) { - yml := `security: $ref: #borked` @@ -178,7 +172,6 @@ func TestOperation_Build_FailSecurity(t *testing.T) { } func TestOperation_Build_FailServers(t *testing.T) { - yml := `servers: $ref: #borked` @@ -195,7 +188,6 @@ func TestOperation_Build_FailServers(t *testing.T) { } func TestOperation_Hash_n_Grab(t *testing.T) { - yml := `tags: - nice - rice @@ -282,14 +274,12 @@ x-mint: sweet` assert.True(t, n.GetDeprecated().Value) assert.Len(t, n.GetExtensions(), 1) assert.Len(t, n.GetServers().Value.([]low.ValueReference[*Server]), 1) - assert.Len(t, n.GetCallbacks().Value, 1) - assert.Len(t, n.GetResponses().Value.(*Responses).Codes, 1) + assert.Equal(t, 1, n.GetCallbacks().Value.Len()) + assert.Equal(t, 1, n.GetResponses().Value.(*Responses).Codes.Len()) assert.Nil(t, n.FindSecurityRequirement("I do not exist")) - } func TestOperation_EmptySecurity(t *testing.T) { - yml := ` security: []` @@ -305,5 +295,4 @@ security: []` assert.NoError(t, err) assert.Len(t, n.Security.Value, 0) - } diff --git a/datamodel/low/v3/parameter_test.go b/datamodel/low/v3/parameter_test.go index 4769e4c..a241a16 100644 --- a/datamodel/low/v3/parameter_test.go +++ b/datamodel/low/v3/parameter_test.go @@ -5,12 +5,14 @@ package v3 import ( "context" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "testing" ) func TestParameter_Build(t *testing.T) { @@ -172,7 +174,6 @@ func TestParameter_Build_Fail_Content(t *testing.T) { } func TestParameter_Hash_n_grab(t *testing.T) { - yml := `description: michelle, meddy and maddy required: true deprecated: false @@ -275,11 +276,11 @@ content: assert.True(t, n.GetRequired().Value) assert.False(t, n.GetDeprecated().Value) assert.False(t, n.GetAllowEmptyValue().Value) - assert.Len(t, n.GetSchema().Value.(*base.SchemaProxy).Schema().Properties.Value, 3) + assert.Equal(t, 3, n.GetSchema().Value.(*base.SchemaProxy).Schema().Properties.Value.Len()) assert.Equal(t, "beautiful", n.GetStyle().Value) assert.True(t, n.GetAllowReserved().Value) assert.True(t, n.GetExplode().Value) assert.NotNil(t, n.GetExample().Value) - assert.Len(t, n.GetExamples().Value.(map[low.KeyReference[string]]low.ValueReference[*base.Example]), 2) - assert.Len(t, n.GetContent().Value.(map[low.KeyReference[string]]low.ValueReference[*MediaType]), 1) + assert.Equal(t, 2, orderedmap.Cast[low.KeyReference[string], low.ValueReference[*base.Example]](n.GetExamples().Value).Len()) + assert.Equal(t, 1, orderedmap.Cast[low.KeyReference[string], low.ValueReference[*MediaType]](n.GetContent().Value).Len()) } diff --git a/datamodel/low/v3/security_scheme_test.go b/datamodel/low/v3/security_scheme_test.go index 24065b9..d6728ca 100644 --- a/datamodel/low/v3/security_scheme_test.go +++ b/datamodel/low/v3/security_scheme_test.go @@ -5,12 +5,13 @@ package v3 import ( "context" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "testing" ) func TestSecurityRequirement_Build(t *testing.T) { @@ -29,7 +30,7 @@ func TestSecurityRequirement_Build(t *testing.T) { err = n.Build(context.Background(), nil, idxNode.Content[0], idx) assert.NoError(t, err) - assert.Len(t, n.Requirements.Value, 1) + assert.Equal(t, 1, n.Requirements.Value.Len()) assert.Equal(t, "read:me", n.FindRequirement("something")[0].Value) assert.Equal(t, "write:me", n.FindRequirement("something")[1].Value) assert.Nil(t, n.FindRequirement("none")) @@ -72,7 +73,6 @@ x-milk: please` assert.Equal(t, "please", n.FindExtension("x-milk").Value) assert.Equal(t, "https://pb33f.io", n.Flows.Value.Implicit.Value.TokenUrl.Value) assert.Len(t, n.GetExtensions(), 1) - } func TestSecurityScheme_Build_Fail(t *testing.T) { diff --git a/datamodel/low/v3/server_test.go b/datamodel/low/v3/server_test.go index f0c4ffb..b10564b 100644 --- a/datamodel/low/v3/server_test.go +++ b/datamodel/low/v3/server_test.go @@ -5,15 +5,16 @@ package v3 import ( "context" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "testing" ) func TestServer_Build(t *testing.T) { - yml := `x-coffee: hot url: https://pb33f.io description: high quality software for developers. @@ -48,11 +49,9 @@ variables: low.GenerateHashString(s.Value)) assert.Len(t, n.GetExtensions(), 1) - } func TestServer_Build_NoVars(t *testing.T) { - yml := `url: https://pb33f.io description: high quality software for developers.` @@ -68,6 +67,5 @@ description: high quality software for developers.` assert.NoError(t, err) assert.Equal(t, "https://pb33f.io", n.URL.Value) assert.Equal(t, "high quality software for developers.", n.Description.Value) - assert.Len(t, n.Variables.Value, 0) - + assert.Equal(t, 0, orderedmap.Len(n.Variables.Value)) } diff --git a/orderedmap/orderedmap.go b/orderedmap/orderedmap.go index 08680a9..d50c613 100644 --- a/orderedmap/orderedmap.go +++ b/orderedmap/orderedmap.go @@ -42,9 +42,11 @@ type wrapPair[K comparable, V any] struct { *wk8orderedmap.Pair[K, V] } -type ActionFunc[K comparable, V any] func(Pair[K, V]) error -type TranslateFunc[IN any, OUT any] func(IN) (OUT, error) -type ResultFunc[V any] func(V) error +type ( + ActionFunc[K comparable, V any] func(Pair[K, V]) error + TranslateFunc[IN any, OUT any] func(IN) (OUT, error) + ResultFunc[V any] func(V) error +) // New creates an ordered map generic object. func New[K comparable, V any]() Map[K, V] { @@ -63,6 +65,10 @@ func (o *wrapOrderedMap[K, V]) GetOrZero(k K) V { } func (o *wrapOrderedMap[K, V]) First() Pair[K, V] { + if o == nil { + return nil + } + pair := o.OrderedMap.Oldest() if pair == nil { return nil @@ -76,7 +82,7 @@ func (o *wrapOrderedMap[K, V]) First() Pair[K, V] { func NewPair[K comparable, V any](key K, value V) Pair[K, V] { return &wrapPair[K, V]{ Pair: &wk8orderedmap.Pair[K, V]{ - Key: key, + Key: key, Value: value, }, } @@ -174,6 +180,20 @@ func First[K comparable, V any](m Map[K, V]) Pair[K, V] { return m.First() } +// Cast converts `any` to `Map`. +func Cast[K comparable, V any](v any) Map[K, V] { + if v == nil { + return nil + } + + m, ok := v.(*wrapOrderedMap[K, V]) + if !ok { + return nil + } + + return m +} + type jobStatus[T any] struct { done chan struct{} result T diff --git a/what-changed/model/comparison_functions.go b/what-changed/model/comparison_functions.go index 0a732e1..a151c4f 100644 --- a/what-changed/model/comparison_functions.go +++ b/what-changed/model/comparison_functions.go @@ -26,8 +26,8 @@ var changeMutex sync.Mutex // CreateChange is a generic function that will create a Change of type T, populate all properties if set, and then // add a pointer to Change[T] in the slice of Change pointers provided func CreateChange(changes *[]*Change, changeType int, property string, leftValueNode, rightValueNode *yaml.Node, - breaking bool, originalObject, newObject any) *[]*Change { - + breaking bool, originalObject, newObject any, +) *[]*Change { // create a new context for the left and right nodes. ctx := CreateContext(leftValueNode, rightValueNode) c := &Change{ @@ -71,7 +71,8 @@ func CreateContext(l, r *yaml.Node) *ChangeContext { } func FlattenLowLevelMap[T any]( - lowMap map[low.KeyReference[string]]low.ValueReference[T]) map[string]*low.ValueReference[T] { + lowMap map[low.KeyReference[string]]low.ValueReference[T], +) map[string]*low.ValueReference[T] { flat := make(map[string]*low.ValueReference[T]) for i := range lowMap { l := lowMap[i] @@ -80,6 +81,19 @@ func FlattenLowLevelMap[T any]( return flat } +func FlattenLowLevelOrderedMap[T any]( + lowMap orderedmap.Map[low.KeyReference[string], low.ValueReference[T]], +) map[string]*low.ValueReference[T] { + flat := make(map[string]*low.ValueReference[T]) + + for pair := orderedmap.First(lowMap); pair != nil; pair = pair.Next() { + k := pair.Key() + l := pair.Value() + flat[k.Value] = &l + } + return flat +} + // CountBreakingChanges counts the number of changes in a slice that are breaking func CountBreakingChanges(changes []*Change) int { b := 0 @@ -99,7 +113,8 @@ func CountBreakingChanges(changes []*Change) int { // properties like descriptions, summaries and other non-binding values, so a breakingRemove value can be tuned for // these circumstances. func CheckForObjectAdditionOrRemoval[T any](l, r map[string]*low.ValueReference[T], label string, changes *[]*Change, - breakingAdd, breakingRemove bool) { + breakingAdd, breakingRemove bool, +) { var left, right T if CheckSpecificObjectRemoved(l, r, label) { left = l[label].GetValue() @@ -129,7 +144,6 @@ func CheckSpecificObjectAdded[T any](l, r map[string]*T, label string) bool { // CheckPropertyAdditionOrRemoval // CheckForModification func CheckProperties(properties []*PropertyCheck) { - // todo: make this async to really speed things up. for _, n := range properties { CheckPropertyAdditionOrRemoval(n.LeftNode, n.RightNode, n.Label, n.Changes, n.Breaking, n.Original, n.New) @@ -139,7 +153,8 @@ func CheckProperties(properties []*PropertyCheck) { // CheckPropertyAdditionOrRemoval will run both CheckForRemoval (first) and CheckForAddition (second) func CheckPropertyAdditionOrRemoval[T any](l, r *yaml.Node, - label string, changes *[]*Change, breaking bool, orig, new T) { + label string, changes *[]*Change, breaking bool, orig, new T, +) { CheckForRemoval[T](l, r, label, changes, breaking, orig, new) CheckForAddition[T](l, r, label, changes, breaking, orig, new) } @@ -238,12 +253,14 @@ func CheckForModification[T any](l, r *yaml.Node, label string, changes *[]*Chan // CheckMapForChanges checks a left and right low level map for any additions, subtractions or modifications to // values. The compareFunc argument should reference the correct comparison function for the generic type. func CheckMapForChanges[T any, R any](expLeft, expRight orderedmap.Map[low.KeyReference[string], low.ValueReference[T]], - changes *[]*Change, label string, compareFunc func(l, r T) R) map[string]R { + changes *[]*Change, label string, compareFunc func(l, r T) R, +) map[string]R { return CheckMapForChangesWithComp(expLeft, expRight, changes, label, compareFunc, true) } func CheckMapForAdditionRemoval[T any](expLeft, expRight orderedmap.Map[low.KeyReference[string], low.ValueReference[T]], - changes *[]*Change, label string) any { + changes *[]*Change, label string, +) any { // do nothing doNothing := func(l, r T) any { return nil @@ -266,8 +283,8 @@ func CheckMapForAdditionRemoval[T any](expLeft, expRight orderedmap.Map[low.KeyR // values. The compareFunc argument should reference the correct comparison function for the generic type. The compare // bit determines if the comparison should be run or not. func CheckMapForChangesWithComp[T any, R any](expLeft, expRight orderedmap.Map[low.KeyReference[string], low.ValueReference[T]], - changes *[]*Change, label string, compareFunc func(l, r T) R, compare bool) map[string]R { - + changes *[]*Change, label string, compareFunc func(l, r T) R, compare bool, +) map[string]R { // stop concurrent threads screwing up changes. var chLock sync.Mutex @@ -331,7 +348,7 @@ func CheckMapForChangesWithComp[T any, R any](expLeft, expRight orderedmap.Map[l go checkLeft(k, doneChan, lHashes, rHashes, lValues, rValues) } - //check right example hashes + // check right example hashes for k := range rHashes { count++ go checkRightValue(k, doneChan, lHashes, rValues, changes, label, &chLock) @@ -349,8 +366,8 @@ func CheckMapForChangesWithComp[T any, R any](expLeft, expRight orderedmap.Map[l } func checkRightValue[T any](k string, doneChan chan bool, f map[string]string, p map[string]low.ValueReference[T], - changes *[]*Change, label string, lock *sync.Mutex) { - + changes *[]*Change, label string, lock *sync.Mutex, +) { lhash := f[k] if lhash == "" { lock.Lock() @@ -367,7 +384,8 @@ func checkRightValue[T any](k string, doneChan chan bool, f map[string]string, p // ExtractStringValueSliceChanges will compare two low level string slices for changes. func ExtractStringValueSliceChanges(lParam, rParam []low.ValueReference[string], - changes *[]*Change, label string, breaking bool) { + changes *[]*Change, label string, breaking bool, +) { lKeys := make([]string, len(lParam)) rKeys := make([]string, len(rParam)) lValues := make(map[string]low.ValueReference[string]) @@ -404,7 +422,8 @@ func ExtractStringValueSliceChanges(lParam, rParam []low.ValueReference[string], // ExtractRawValueSliceChanges will compare two low level interface{} slices for changes. func ExtractRawValueSliceChanges(lParam, rParam []low.ValueReference[any], - changes *[]*Change, label string, breaking bool) { + changes *[]*Change, label string, breaking bool, +) { lKeys := make([]string, len(lParam)) rKeys := make([]string, len(rParam)) lValues := make(map[string]low.ValueReference[any]) diff --git a/what-changed/model/discriminator.go b/what-changed/model/discriminator.go index d267541..fd5fb04 100644 --- a/what-changed/model/discriminator.go +++ b/what-changed/model/discriminator.go @@ -64,8 +64,8 @@ func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges { CheckProperties(props) // flatten maps - lMap := FlattenLowLevelMap[string](l.Mapping.Value) - rMap := FlattenLowLevelMap[string](r.Mapping.Value) + lMap := FlattenLowLevelOrderedMap[string](l.Mapping.Value) + rMap := FlattenLowLevelOrderedMap[string](r.Mapping.Value) // check for removals, modifications and moves for i := range lMap { @@ -92,5 +92,4 @@ func CompareDiscriminator(l, r *base.Discriminator) *DiscriminatorChanges { return nil } return dc - } diff --git a/what-changed/model/oauth_flows.go b/what-changed/model/oauth_flows.go index 5c9345b..bb71edf 100644 --- a/what-changed/model/oauth_flows.go +++ b/what-changed/model/oauth_flows.go @@ -6,6 +6,7 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" ) // OAuthFlowsChanges represents changes found between two OpenAPI OAuthFlows objects. @@ -228,26 +229,26 @@ func CompareOAuthFlow(l, r *v3.OAuthFlow) *OAuthFlowChanges { CheckProperties(props) - for k, v := range l.Scopes.Value { - if r != nil && r.FindScope(k.Value) == nil { + for pair := orderedmap.First(l.Scopes.Value); pair != nil; pair = pair.Next() { + if r != nil && r.FindScope(pair.Key().Value) == nil { CreateChange(&changes, ObjectRemoved, v3.Scopes, - v.ValueNode, nil, true, - k.Value, nil) + pair.Value().ValueNode, nil, true, + pair.Key().Value, nil) continue } - if r != nil && r.FindScope(k.Value) != nil { - if v.Value != r.FindScope(k.Value).Value { + if r != nil && r.FindScope(pair.Key().Value) != nil { + if pair.Value().Value != r.FindScope(pair.Key().Value).Value { CreateChange(&changes, Modified, v3.Scopes, - v.ValueNode, r.FindScope(k.Value).ValueNode, true, - v.Value, r.FindScope(k.Value).Value) + pair.Value().ValueNode, r.FindScope(pair.Key().Value).ValueNode, true, + pair.Value().Value, r.FindScope(pair.Key().Value).Value) } } } - for k, v := range r.Scopes.Value { - if l != nil && l.FindScope(k.Value) == nil { + for pair := orderedmap.First(r.Scopes.Value); pair != nil; pair = pair.Next() { + if l != nil && l.FindScope(pair.Key().Value) == nil { CreateChange(&changes, ObjectAdded, v3.Scopes, - nil, v.ValueNode, false, - nil, k.Value) + nil, pair.Value().ValueNode, false, + nil, pair.Key().Value) } } oa := new(OAuthFlowChanges)