diff --git a/datamodel/high/base/example.go b/datamodel/high/base/example.go index 7ade3b4..6f5a8c9 100644 --- a/datamodel/high/base/example.go +++ b/datamodel/high/base/example.go @@ -7,6 +7,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high" lowmodel "github.com/pb33f/libopenapi/datamodel/low" low "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -57,10 +58,10 @@ func (e *Example) MarshalYAML() (interface{}, error) { // ExtractExamples will convert a low-level example map, into a high level one that is simple to navigate. // no fidelity is lost, everything is still available via GoLow() -func ExtractExamples(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Example]) map[string]*Example { - extracted := make(map[string]*Example) - for k, v := range elements { - extracted[k.Value] = NewExample(v.Value) +func ExtractExamples(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Example]]) orderedmap.Map[string, *Example] { + extracted := orderedmap.New[string, *Example]() + for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() { + extracted.Set(pair.Key().Value, NewExample(pair.Value().Value)) } return extracted } diff --git a/datamodel/high/base/example_test.go b/datamodel/high/base/example_test.go index a783c0a..204f319 100644 --- a/datamodel/high/base/example_test.go +++ b/datamodel/high/base/example_test.go @@ -5,12 +5,14 @@ package base import ( "fmt" - 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" + + lowmodel "github.com/pb33f/libopenapi/datamodel/low" + lowbase "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) func TestNewExample(t *testing.T) { @@ -61,14 +63,13 @@ func TestExtractExamples(t *testing.T) { _ = lowExample.Build(nil, cNode.Content[0], nil) - examplesMap := make(map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*lowbase.Example]) - examplesMap[lowmodel.KeyReference[string]{ - Value: "green", - }] = lowmodel.ValueReference[*lowbase.Example]{ - Value: &lowExample, - } + examplesMap := orderedmap.New[lowmodel.KeyReference[string], lowmodel.ValueReference[*lowbase.Example]]() + examplesMap.Set( + lowmodel.KeyReference[string]{Value: "green"}, + lowmodel.ValueReference[*lowbase.Example]{Value: &lowExample}, + ) - assert.Equal(t, "herbs", ExtractExamples(examplesMap)["green"].Summary) + assert.Equal(t, "herbs", ExtractExamples(examplesMap).GetOrZero("green").Summary) } diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index e10e04c..adb5ad6 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -7,6 +7,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high" lowmodel "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -50,16 +51,16 @@ type Schema struct { PrefixItems []*SchemaProxy `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"` // 3.1 Specific properties - Contains *SchemaProxy `json:"contains,omitempty" yaml:"contains,omitempty"` - MinContains *int64 `json:"minContains,omitempty" yaml:"minContains,omitempty"` - MaxContains *int64 `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` - If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"` - Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"` - Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"` - DependentSchemas map[string]*SchemaProxy `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` - PatternProperties map[string]*SchemaProxy `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` - PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` - UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` + Contains *SchemaProxy `json:"contains,omitempty" yaml:"contains,omitempty"` + MinContains *int64 `json:"minContains,omitempty" yaml:"minContains,omitempty"` + MaxContains *int64 `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` + If *SchemaProxy `json:"if,omitempty" yaml:"if,omitempty"` + Else *SchemaProxy `json:"else,omitempty" yaml:"else,omitempty"` + Then *SchemaProxy `json:"then,omitempty" yaml:"then,omitempty"` + DependentSchemas orderedmap.Map[string, *SchemaProxy] `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` + PatternProperties orderedmap.Map[string, *SchemaProxy] `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` + PropertyNames *SchemaProxy `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` + UnevaluatedItems *SchemaProxy `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` // in 3.1 UnevaluatedProperties can be a Schema or a boolean // https://github.com/pb33f/libopenapi/issues/118 @@ -72,35 +73,35 @@ type Schema struct { Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"` // Compatible with all versions - Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"` - Properties map[string]*SchemaProxy `json:"properties,omitempty" yaml:"properties,omitempty"` - Title string `json:"title,omitempty" yaml:"title,omitempty"` - MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` - Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` - Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` - MaxLength *int64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` - MinLength *int64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` - Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` - Format string `json:"format,omitempty" yaml:"format,omitempty"` - MaxItems *int64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` - MinItems *int64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` - UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` - MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` - MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` - Required []string `json:"required,omitempty" yaml:"required,omitempty"` - Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` - AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"` - Const any `json:"const,omitempty" yaml:"const,renderZero,omitempty"` - Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` - ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 - WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 - XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` - ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` - Example any `json:"example,omitempty" yaml:"example,omitempty"` - Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Not *SchemaProxy `json:"not,omitempty" yaml:"not,omitempty"` + Properties orderedmap.Map[string, *SchemaProxy] `json:"properties,omitempty" yaml:"properties,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + MaxLength *int64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + MinLength *int64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + MaxItems *int64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + MinItems *int64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + MaxProperties *int64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + MinProperties *int64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + AdditionalProperties any `json:"additionalProperties,omitempty" yaml:"additionalProperties,renderZero,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Default any `json:"default,omitempty" yaml:"default,renderZero,omitempty"` + Const any `json:"const,omitempty" yaml:"const,renderZero,omitempty"` + Nullable *bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` // https://github.com/pb33f/libopenapi/issues/30 + XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` + ExternalDocs *ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *base.Schema // Parent Proxy refers back to the low level SchemaProxy that is proxying this schema. @@ -356,14 +357,13 @@ func NewSchema(schema *base.Schema) *Schema { // props async buildProps := func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy], - props map[string]*SchemaProxy, sw int, + props orderedmap.Map[string, *SchemaProxy], sw int, ) { - props[k.Value] = NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{ + props.Set(k.Value, NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{ Value: v.Value, KeyNode: k.KeyNode, ValueNode: v.ValueNode, - }, - ) + })) switch sw { case 0: @@ -375,18 +375,18 @@ func NewSchema(schema *base.Schema) *Schema { } } - props := make(map[string]*SchemaProxy) - for k, v := range schema.Properties.Value { - buildProps(k, v, props, 0) + props := orderedmap.New[string, *SchemaProxy]() + for pair := orderedmap.First(schema.Properties.Value); pair != nil; pair = pair.Next() { + buildProps(pair.Key(), pair.Value(), props, 0) } - dependents := make(map[string]*SchemaProxy) - for k, v := range schema.DependentSchemas.Value { - buildProps(k, v, dependents, 1) + dependents := orderedmap.New[string, *SchemaProxy]() + for pair := orderedmap.First(schema.DependentSchemas.Value); pair != nil; pair = pair.Next() { + buildProps(pair.Key(), pair.Value(), dependents, 1) } - patternProps := make(map[string]*SchemaProxy) - for k, v := range schema.PatternProperties.Value { - buildProps(k, v, patternProps, 2) + patternProps := orderedmap.New[string, *SchemaProxy]() + for pair := orderedmap.First(schema.PatternProperties.Value); pair != nil; pair = pair.Next() { + buildProps(pair.Key(), pair.Value(), patternProps, 2) } var allOf []*SchemaProxy diff --git a/datamodel/high/base/schema_test.go b/datamodel/high/base/schema_test.go index ccea88c..0e4df24 100644 --- a/datamodel/high/base/schema_test.go +++ b/datamodel/high/base/schema_test.go @@ -303,8 +303,8 @@ $anchor: anchor` 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.PatternProperties.GetOrZero("patternOne").Schema().Type[0]) + assert.Equal(t, "string", compiled.DependentSchemas.GetOrZero("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.A.Schema().Type[0]) @@ -416,7 +416,7 @@ anyOf: anyOfB: type: string description: anyOfB description - example: 'anyOfBExp' + example: 'anyOfBExp' not: type: object description: a not thing @@ -428,7 +428,7 @@ not: notB: type: string description: notB description - example: 'notBExp' + example: 'notBExp' items: type: object description: an items thing @@ -454,7 +454,7 @@ properties: somethingBProp: exclusiveMinimum: 3 exclusiveMaximum: 120 - type: + type: - string - null description: something b subprop @@ -466,9 +466,9 @@ properties: attribute: true wrapped: false x-pizza: love - additionalProperties: + additionalProperties: why: yes - thatIs: true + thatIs: true additionalProperties: type: string description: nice @@ -500,9 +500,9 @@ required: [cake, fish]` assert.Nil(t, schemaProxy.GetBuildError()) assert.True(t, compiled.ExclusiveMaximum.A) - assert.Equal(t, float64(123), compiled.Properties["somethingB"].Schema().ExclusiveMinimum.B) - assert.Equal(t, float64(334), compiled.Properties["somethingB"].Schema().ExclusiveMaximum.B) - assert.Len(t, compiled.Properties["somethingB"].Schema().Properties["somethingBProp"].Schema().Type, 2) + assert.Equal(t, float64(123), compiled.Properties.GetOrZero("somethingB").Schema().ExclusiveMinimum.B) + assert.Equal(t, float64(334), compiled.Properties.GetOrZero("somethingB").Schema().ExclusiveMaximum.B) + assert.Len(t, compiled.Properties.GetOrZero("somethingB").Schema().Properties.GetOrZero("somethingBProp").Schema().Type, 2) assert.Equal(t, "nice", compiled.AdditionalProperties.(*SchemaProxy).Schema().Description) @@ -734,7 +734,7 @@ properties: highSchema := NewSchema(&lowSchema) // print out the description of 'aProperty' - fmt.Print(highSchema.Properties["aProperty"].Schema().Description) + fmt.Print(highSchema.Properties.GetOrZero("aProperty").Schema().Description) // Output: this is an integer property } @@ -765,7 +765,7 @@ properties: }) // print out the description of 'aProperty' - fmt.Print(highSchema.Schema().Properties["aProperty"].Schema().Description) + fmt.Print(highSchema.Schema().Properties.GetOrZero("aProperty").Schema().Description) // Output: this is an integer property } diff --git a/datamodel/high/base/security_requirement.go b/datamodel/high/base/security_requirement.go index b9c4783..a4564df 100644 --- a/datamodel/high/base/security_requirement.go +++ b/datamodel/high/base/security_requirement.go @@ -4,11 +4,13 @@ package base import ( + "sort" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" ) // SecurityRequirement is a high-level representation of a Swagger / OpenAPI 3 SecurityRequirement object. @@ -19,7 +21,7 @@ import ( // The name used for each property MUST correspond to a security scheme declared in the Security Definitions // - https://swagger.io/specification/v2/#securityDefinitionsObject type SecurityRequirement struct { - Requirements map[string][]string `json:"-" yaml:"-"` + Requirements orderedmap.Map[string, []string] `json:"-" yaml:"-"` low *base.SecurityRequirement } @@ -27,14 +29,14 @@ type SecurityRequirement struct { func NewSecurityRequirement(req *base.SecurityRequirement) *SecurityRequirement { r := new(SecurityRequirement) r.low = req - values := make(map[string][]string) + values := orderedmap.New[string, []string]() // to keep things fast, avoiding copying anything - makes it a little hard to read. - for reqK := range req.Requirements.Value { + for pair := orderedmap.First(req.Requirements.Value); pair != nil; pair = pair.Next() { var vals []string - for valK := range req.Requirements.Value[reqK].Value { - vals = append(vals, req.Requirements.Value[reqK].Value[valK].Value) + for valK := range pair.Value().Value { + vals = append(vals, pair.Value().Value[valK].Value) } - values[reqK.Value] = vals + values.Set(pair.Key().Value, vals) } r.Requirements = values return r @@ -67,24 +69,23 @@ func (s *SecurityRequirement) MarshalYAML() (interface{}, error) { } m := utils.CreateEmptyMapNode() - keys := make([]*req, len(s.Requirements)) + keys := make([]*req, orderedmap.Len(s.Requirements)) i := 0 - for k := range s.Requirements { - keys[i] = &req{key: k, val: s.Requirements[k]} + for pair := orderedmap.First(s.Requirements); pair != nil; pair = pair.Next() { + keys[i] = &req{key: pair.Key(), val: pair.Value()} i++ } i = 0 if s.low != nil { for o := range keys { kv := keys[o].key - for k := range s.low.Requirements.Value { - if k.Value == kv { - gh := s.low.Requirements.Value[k] - keys[o].line = k.KeyNode.Line - keys[o].lowKey = &k - keys[o].lowVal = &gh + for pair := orderedmap.First(s.low.Requirements.Value); pair != nil; pair = pair.Next() { + if pair.Key().Value == kv { + keys[o].line = pair.Key().KeyNode.Line + keys[o].lowKey = pair.KeyPtr() + keys[o].lowVal = pair.ValuePtr() } i++ } diff --git a/datamodel/high/base/security_requirement_test.go b/datamodel/high/base/security_requirement_test.go index b2a4f4e..b6f8b71 100644 --- a/datamodel/high/base/security_requirement_test.go +++ b/datamodel/high/base/security_requirement_test.go @@ -4,12 +4,14 @@ package base import ( - 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" + + lowmodel "github.com/pb33f/libopenapi/datamodel/low" + lowbase "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) func TestNewSecurityRequirement(t *testing.T) { @@ -32,11 +34,11 @@ cake: highExt := NewSecurityRequirement(&lowExt) - assert.Len(t, highExt.Requirements["pizza"], 2) - assert.Len(t, highExt.Requirements["cake"], 2) + assert.Len(t, highExt.Requirements.GetOrZero("pizza"), 2) + assert.Len(t, highExt.Requirements.GetOrZero("cake"), 2) wentLow := highExt.GoLow() - assert.Len(t, wentLow.Requirements.Value, 2) + assert.Equal(t, 2, orderedmap.Len(wentLow.Requirements.Value)) assert.NotNil(t, highExt.GoLowUntyped()) // render the high-level object as YAML diff --git a/datamodel/high/node_builder.go b/datamodel/high/node_builder.go index 0a71b08..b456f7e 100644 --- a/datamodel/high/node_builder.go +++ b/datamodel/high/node_builder.go @@ -5,14 +5,15 @@ package high import ( "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "reflect" "sort" "strconv" "strings" "unicode" + + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/utils" + "gopkg.in/yaml.v3" ) // NodeEntry represents a single node used by NodeBuilder. @@ -659,12 +660,12 @@ func (n *NodeBuilder) extractLowMapKeys(fg reflect.Value, x string, found bool, return found, orderedCollection } -// Renderable is an interface that can be implemented by types that provide a custom MarshaYAML method. +// Renderable is an interface that can be implemented by types that provide a custom MarshalYAML method. type Renderable interface { MarshalYAML() (interface{}, error) } -// RenderableInline is an interface that can be implemented by types that provide a custom MarshaYAML method. +// RenderableInline is an interface that can be implemented by types that provide a custom MarshalYAML method. type RenderableInline interface { MarshalYAMLInline() (interface{}, error) } diff --git a/datamodel/high/v2/asyncresult.go b/datamodel/high/v2/asyncresult.go new file mode 100644 index 0000000..d9193d7 --- /dev/null +++ b/datamodel/high/v2/asyncresult.go @@ -0,0 +1,6 @@ +package v2 + +type asyncResult[T any] struct { + key string + result T +} diff --git a/datamodel/high/v2/definitions.go b/datamodel/high/v2/definitions.go index 4021711..138ec5a 100644 --- a/datamodel/high/v2/definitions.go +++ b/datamodel/high/v2/definitions.go @@ -8,6 +8,7 @@ import ( lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowbase "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" ) // Definitions is a high-level represents of a Swagger / OpenAPI 2 Definitions object, backed by a low-level one. @@ -16,7 +17,7 @@ import ( // arrays or models. // - https://swagger.io/specification/v2/#definitionsObject type Definitions struct { - Definitions map[string]*highbase.SchemaProxy + Definitions orderedmap.Map[string, *highbase.SchemaProxy] low *low.Definitions } @@ -24,12 +25,20 @@ type Definitions struct { func NewDefinitions(definitions *low.Definitions) *Definitions { rd := new(Definitions) rd.low = definitions - defs := make(map[string]*highbase.SchemaProxy) - for k := range definitions.Schemas { - defs[k.Value] = highbase.NewSchemaProxy(&lowmodel.NodeReference[*lowbase.SchemaProxy]{ - Value: definitions.Schemas[k].Value, - }) + defs := orderedmap.New[string, *highbase.SchemaProxy]() + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*lowbase.SchemaProxy]]) (asyncResult[*highbase.SchemaProxy], error) { + return asyncResult[*highbase.SchemaProxy]{ + key: pair.Key().Value, + result: highbase.NewSchemaProxy(&lowmodel.NodeReference[*lowbase.SchemaProxy]{ + Value: pair.Value().Value, + }), + }, nil } + resultFunc := func(value asyncResult[*highbase.SchemaProxy]) error { + defs.Set(value.key, value.result) + return nil + } + _ = orderedmap.TranslateMapParallel(definitions.Schemas, translateFunc, resultFunc) rd.Definitions = defs return rd } diff --git a/datamodel/high/v2/examples.go b/datamodel/high/v2/examples.go index f80c8e5..55580b6 100644 --- a/datamodel/high/v2/examples.go +++ b/datamodel/high/v2/examples.go @@ -3,13 +3,16 @@ package v2 -import low "github.com/pb33f/libopenapi/datamodel/low/v2" +import ( + low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" +) // Example represents a high-level Swagger / OpenAPI 2 Example object, backed by a low level one. // Allows sharing examples for operation responses // - https://swagger.io/specification/v2/#exampleObject type Example struct { - Values map[string]any + Values orderedmap.Map[string, any] low *low.Examples } @@ -17,10 +20,10 @@ type Example struct { func NewExample(examples *low.Examples) *Example { e := new(Example) e.low = examples - if len(examples.Values) > 0 { - values := make(map[string]any) - for k := range examples.Values { - values[k.Value] = examples.Values[k].Value + if orderedmap.Len(examples.Values) > 0 { + values := orderedmap.New[string, any]() + for pair := orderedmap.First(examples.Values); pair != nil; pair = pair.Next() { + values.Set(pair.Key().Value, pair.Value().Value) } e.Values = values } diff --git a/datamodel/high/v2/parameter_definitions.go b/datamodel/high/v2/parameter_definitions.go index 0d741f1..cf3e86e 100644 --- a/datamodel/high/v2/parameter_definitions.go +++ b/datamodel/high/v2/parameter_definitions.go @@ -3,7 +3,11 @@ package v2 -import low "github.com/pb33f/libopenapi/datamodel/low/v2" +import ( + lowmodel "github.com/pb33f/libopenapi/datamodel/low" + low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" +) // ParameterDefinitions is a high-level representation of a Swagger / OpenAPI 2 Parameters Definitions object // that is backed by a low-level one. @@ -12,7 +16,7 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v2" // referenced to the ones defined here. It does not define global operation parameters // - https://swagger.io/specification/v2/#parametersDefinitionsObject type ParameterDefinitions struct { - Definitions map[string]*Parameter + Definitions orderedmap.Map[string, *Parameter] low *low.ParameterDefinitions } @@ -21,26 +25,18 @@ type ParameterDefinitions struct { func NewParametersDefinitions(parametersDefinitions *low.ParameterDefinitions) *ParameterDefinitions { pd := new(ParameterDefinitions) pd.low = parametersDefinitions - params := make(map[string]*Parameter) - var buildParam = func(name string, param *low.Parameter, rChan chan<- asyncResult[*Parameter]) { - rChan <- asyncResult[*Parameter]{ - key: name, - result: NewParameter(param), - } + params := orderedmap.New[string, *Parameter]() + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Parameter]]) (asyncResult[*Parameter], error) { + return asyncResult[*Parameter]{ + key: pair.Key().Value, + result: NewParameter(pair.Value().Value), + }, nil } - resChan := make(chan asyncResult[*Parameter]) - for k := range parametersDefinitions.Definitions { - go buildParam(k.Value, parametersDefinitions.Definitions[k].Value, resChan) - } - totalParams := len(parametersDefinitions.Definitions) - completedParams := 0 - for completedParams < totalParams { - select { - case r := <-resChan: - completedParams++ - params[r.key] = r.result - } + resultFunc := func(value asyncResult[*Parameter]) error { + params.Set(value.key, value.result) + return nil } + _ = orderedmap.TranslateMapParallel(parametersDefinitions.Definitions, translateFunc, resultFunc) pd.Definitions = params return pd } diff --git a/datamodel/high/v2/response.go b/datamodel/high/v2/response.go index 4e460ce..5e2d4bc 100644 --- a/datamodel/high/v2/response.go +++ b/datamodel/high/v2/response.go @@ -7,6 +7,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high/base" low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" ) // Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one. @@ -15,7 +16,7 @@ import ( type Response struct { Description string Schema *base.SchemaProxy - Headers map[string]*Header + Headers orderedmap.Map[string, *Header] Examples *Example Extensions map[string]any low *low.Response @@ -33,9 +34,9 @@ func NewResponse(response *low.Response) *Response { r.Schema = base.NewSchemaProxy(&response.Schema) } if !response.Headers.IsEmpty() { - headers := make(map[string]*Header) - for k := range response.Headers.Value { - headers[k.Value] = NewHeader(response.Headers.Value[k].Value) + headers := orderedmap.New[string, *Header]() + for pair := orderedmap.First(response.Headers.Value); pair != nil; pair = pair.Next() { + headers.Set(pair.Key().Value, NewHeader(pair.Value().Value)) } r.Headers = headers } diff --git a/datamodel/high/v2/responses.go b/datamodel/high/v2/responses.go index dc1d647..b6cc9ec 100644 --- a/datamodel/high/v2/responses.go +++ b/datamodel/high/v2/responses.go @@ -5,12 +5,14 @@ package v2 import ( "github.com/pb33f/libopenapi/datamodel/high" + lowmodel "github.com/pb33f/libopenapi/datamodel/low" low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" ) // Responses is a high-level representation of a Swagger / OpenAPI 2 Responses object, backed by a low level one. type Responses struct { - Codes map[string]*Response + Codes orderedmap.Map[string, *Response] Default *Response Extensions map[string]any low *low.Responses @@ -22,36 +24,26 @@ func NewResponses(responses *low.Responses) *Responses { r.low = responses r.Extensions = high.ExtractExtensions(responses.Extensions) - // async function. - var buildPath = func(code string, pi *low.Response, rChan chan<- asyncResult[*Response]) { - rChan <- asyncResult[*Response]{ - key: code, - result: NewResponse(pi), - } - } - if !responses.Default.IsEmpty() { r.Default = NewResponse(responses.Default.Value) } - // run everything async. lots of responses with lots of data are possible. - if len(responses.Codes) > 0 { - resultChan := make(chan asyncResult[*Response]) - for k := range responses.Codes { - go buildPath(k.Value, responses.Codes[k].Value, resultChan) + if orderedmap.Len(responses.Codes) > 0 { + resp := orderedmap.New[string, *Response]() + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Response]]) (asyncResult[*Response], error) { + return asyncResult[*Response]{ + key: pair.Key().Value, + result: NewResponse(pair.Value().Value), + }, nil } - resp := make(map[string]*Response) - totalResponses := len(responses.Codes) - completedResponses := 0 - for completedResponses < totalResponses { - select { - case res := <-resultChan: - completedResponses++ - resp[res.key] = res.result - } + resultFunc := func(value asyncResult[*Response]) error { + resp.Set(value.key, value.result) + return nil } + _ = orderedmap.TranslateMapParallel(responses.Codes, translateFunc, resultFunc) r.Codes = resp } + return r } diff --git a/datamodel/high/v2/responses_definitions.go b/datamodel/high/v2/responses_definitions.go index 94fb819..fd8ff63 100644 --- a/datamodel/high/v2/responses_definitions.go +++ b/datamodel/high/v2/responses_definitions.go @@ -3,7 +3,11 @@ package v2 -import low "github.com/pb33f/libopenapi/datamodel/low/v2" +import ( + lowmodel "github.com/pb33f/libopenapi/datamodel/low" + low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" +) // ResponsesDefinitions is a high-level representation of a Swagger / OpenAPI 2 Responses Definitions object. // that is backed by a low-level one. @@ -12,7 +16,7 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v2" // referenced to the ones defined here. It does not define global operation responses // - https://swagger.io/specification/v2/#responsesDefinitionsObject type ResponsesDefinitions struct { - Definitions map[string]*Response + Definitions orderedmap.Map[string, *Response] low *low.ResponsesDefinitions } @@ -20,28 +24,19 @@ type ResponsesDefinitions struct { func NewResponsesDefinitions(responsesDefinitions *low.ResponsesDefinitions) *ResponsesDefinitions { rd := new(ResponsesDefinitions) rd.low = responsesDefinitions + responses := orderedmap.New[string, *Response]() + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Response]]) (asyncResult[*Response], error) { + return asyncResult[*Response]{ + key: pair.Key().Value, + result: NewResponse(pair.Value().Value), + }, nil + } + resultFunc := func(value asyncResult[*Response]) error { + responses.Set(value.key, value.result) + return nil + } - // build everything async. - responses := make(map[string]*Response) - var buildResp = func(name string, resp *low.Response, rChan chan<- asyncResult[*Response]) { - rChan <- asyncResult[*Response]{ - key: name, - result: NewResponse(resp), - } - } - resChan := make(chan asyncResult[*Response]) - for k := range responsesDefinitions.Definitions { - go buildResp(k.Value, responsesDefinitions.Definitions[k].Value, resChan) - } - totalResponses := len(responsesDefinitions.Definitions) - completedResponses := 0 - for completedResponses < totalResponses { - select { - case r := <-resChan: - completedResponses++ - responses[r.key] = r.result - } - } + _ = orderedmap.TranslateMapParallel(responsesDefinitions.Definitions, translateFunc, resultFunc) rd.Definitions = responses return rd } diff --git a/datamodel/high/v2/scopes.go b/datamodel/high/v2/scopes.go index cd82c7e..1197afb 100644 --- a/datamodel/high/v2/scopes.go +++ b/datamodel/high/v2/scopes.go @@ -5,6 +5,7 @@ package v2 import ( low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" ) // Scopes is a high-level representation of a Swagger / OpenAPI 2 OAuth2 Scopes object, that is backed by a low-level one. @@ -12,7 +13,7 @@ import ( // Scopes lists the available scopes for an OAuth2 security scheme. // - https://swagger.io/specification/v2/#scopesObject type Scopes struct { - Values map[string]string + Values orderedmap.Map[string, string] low *low.Scopes } @@ -20,9 +21,9 @@ type Scopes struct { func NewScopes(scopes *low.Scopes) *Scopes { s := new(Scopes) s.low = scopes - scopeValues := make(map[string]string) - for k := range scopes.Values { - scopeValues[k.Value] = scopes.Values[k].Value + scopeValues := orderedmap.New[string, string]() + for pair := orderedmap.First(scopes.Values); pair != nil; pair = pair.Next() { + scopeValues.Set(pair.Key().Value, pair.Value().Value) } s.Values = scopeValues return s diff --git a/datamodel/high/v2/security_definitions.go b/datamodel/high/v2/security_definitions.go index 06ccb61..feba1c8 100644 --- a/datamodel/high/v2/security_definitions.go +++ b/datamodel/high/v2/security_definitions.go @@ -3,7 +3,11 @@ package v2 -import low "github.com/pb33f/libopenapi/datamodel/low/v2" +import ( + lowmodel "github.com/pb33f/libopenapi/datamodel/low" + low "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" +) // SecurityDefinitions is a high-level representation of a Swagger / OpenAPI 2 Security Definitions object, that // is backed by a low-level one. @@ -12,7 +16,7 @@ import low "github.com/pb33f/libopenapi/datamodel/low/v2" // schemes on the operations and only serves to provide the relevant details for each scheme // - https://swagger.io/specification/v2/#securityDefinitionsObject type SecurityDefinitions struct { - Definitions map[string]*SecurityScheme + Definitions orderedmap.Map[string, *SecurityScheme] low *low.SecurityDefinitions } @@ -20,10 +24,23 @@ type SecurityDefinitions struct { func NewSecurityDefinitions(definitions *low.SecurityDefinitions) *SecurityDefinitions { sd := new(SecurityDefinitions) sd.low = definitions - schemes := make(map[string]*SecurityScheme) - for k := range definitions.Definitions { - schemes[k.Value] = NewSecurityScheme(definitions.Definitions[k].Value) + schemes := orderedmap.New[string, *SecurityScheme]() + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.SecurityScheme]]) (asyncResult[*SecurityScheme], error) { + return asyncResult[*SecurityScheme]{ + key: pair.Key().Value, + result: NewSecurityScheme(pair.Value().Value), + }, nil } + resultFunc := func(value asyncResult[*SecurityScheme]) error { + schemes.Set(value.key, value.result) + return nil + } + _ = orderedmap.TranslateMapParallel(definitions.Definitions, translateFunc, resultFunc) + + // schemes := make(map[string]*SecurityScheme) + // for k := range definitions.Definitions { + // schemes[k.Value] = NewSecurityScheme(definitions.Definitions[k].Value) + // } sd.Definitions = schemes return sd } diff --git a/datamodel/high/v2/swagger.go b/datamodel/high/v2/swagger.go index fcbe9f5..65ad816 100644 --- a/datamodel/high/v2/swagger.go +++ b/datamodel/high/v2/swagger.go @@ -173,9 +173,3 @@ func NewSwaggerDocument(document *low.Swagger) *Swagger { func (s *Swagger) GoLow() *low.Swagger { return s.low } - -// everything is build async, this little gem holds the results. -type asyncResult[T any] struct { - key string - result T -} diff --git a/datamodel/high/v2/swagger_test.go b/datamodel/high/v2/swagger_test.go index 14bf06a..75aefc9 100644 --- a/datamodel/high/v2/swagger_test.go +++ b/datamodel/high/v2/swagger_test.go @@ -6,6 +6,7 @@ package v2 import ( "github.com/pb33f/libopenapi/datamodel" v2 "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" "io/ioutil" @@ -80,17 +81,17 @@ func TestNewSwaggerDocument_Parameters(t *testing.T) { initTest() highDoc := NewSwaggerDocument(doc) params := highDoc.Parameters - assert.Len(t, params.Definitions, 1) - assert.Equal(t, "query", params.Definitions["simpleParam"].In) - assert.Equal(t, "simple", params.Definitions["simpleParam"].Name) - assert.Equal(t, "string", params.Definitions["simpleParam"].Type) - assert.Equal(t, "nuggets", params.Definitions["simpleParam"].Extensions["x-chicken"]) + assert.Equal(t, 1, orderedmap.Len(params.Definitions)) + assert.Equal(t, "query", params.Definitions.GetOrZero("simpleParam").In) + assert.Equal(t, "simple", params.Definitions.GetOrZero("simpleParam").Name) + assert.Equal(t, "string", params.Definitions.GetOrZero("simpleParam").Type) + assert.Equal(t, "nuggets", params.Definitions.GetOrZero("simpleParam").Extensions["x-chicken"]) wentLow := params.GoLow() assert.Equal(t, 20, wentLow.FindParameter("simpleParam").ValueNode.Line) assert.Equal(t, 5, wentLow.FindParameter("simpleParam").ValueNode.Column) - wentLower := params.Definitions["simpleParam"].GoLow() + wentLower := params.Definitions.GetOrZero("simpleParam").GoLow() assert.Equal(t, 21, wentLower.Name.ValueNode.Line) assert.Equal(t, 11, wentLower.Name.ValueNode.Column) @@ -100,7 +101,7 @@ func TestNewSwaggerDocument_Security(t *testing.T) { initTest() highDoc := NewSwaggerDocument(doc) assert.Len(t, highDoc.Security, 1) - assert.Len(t, highDoc.Security[0].Requirements["global_auth"], 2) + assert.Len(t, highDoc.Security[0].Requirements.GetOrZero("global_auth"), 2) wentLow := highDoc.Security[0].GoLow() assert.Equal(t, 25, wentLow.Requirements.ValueNode.Line) @@ -111,23 +112,23 @@ func TestNewSwaggerDocument_Security(t *testing.T) { func TestNewSwaggerDocument_Definitions_Security(t *testing.T) { initTest() highDoc := NewSwaggerDocument(doc) - assert.Len(t, highDoc.SecurityDefinitions.Definitions, 3) - assert.Equal(t, "oauth2", highDoc.SecurityDefinitions.Definitions["petstore_auth"].Type) + assert.Equal(t, 3, orderedmap.Len(highDoc.SecurityDefinitions.Definitions)) + assert.Equal(t, "oauth2", highDoc.SecurityDefinitions.Definitions.GetOrZero("petstore_auth").Type) assert.Equal(t, "https://petstore.swagger.io/oauth/authorize", - highDoc.SecurityDefinitions.Definitions["petstore_auth"].AuthorizationUrl) - assert.Equal(t, "implicit", highDoc.SecurityDefinitions.Definitions["petstore_auth"].Flow) - assert.Len(t, highDoc.SecurityDefinitions.Definitions["petstore_auth"].Scopes.Values, 2) + highDoc.SecurityDefinitions.Definitions.GetOrZero("petstore_auth").AuthorizationUrl) + assert.Equal(t, "implicit", highDoc.SecurityDefinitions.Definitions.GetOrZero("petstore_auth").Flow) + assert.Equal(t, 2, orderedmap.Len(highDoc.SecurityDefinitions.Definitions.GetOrZero("petstore_auth").Scopes.Values)) goLow := highDoc.SecurityDefinitions.GoLow() assert.Equal(t, 661, goLow.FindSecurityDefinition("petstore_auth").ValueNode.Line) assert.Equal(t, 5, goLow.FindSecurityDefinition("petstore_auth").ValueNode.Column) - goLower := highDoc.SecurityDefinitions.Definitions["petstore_auth"].GoLow() + goLower := highDoc.SecurityDefinitions.Definitions.GetOrZero("petstore_auth").GoLow() assert.Equal(t, 664, goLower.Scopes.KeyNode.Line) assert.Equal(t, 5, goLower.Scopes.KeyNode.Column) - goLowest := highDoc.SecurityDefinitions.Definitions["petstore_auth"].Scopes.GoLow() + goLowest := highDoc.SecurityDefinitions.Definitions.GetOrZero("petstore_auth").Scopes.GoLow() assert.Equal(t, 665, goLowest.FindScope("read:pets").ValueNode.Line) assert.Equal(t, 18, goLowest.FindScope("read:pets").ValueNode.Column) } @@ -135,34 +136,34 @@ func TestNewSwaggerDocument_Definitions_Security(t *testing.T) { func TestNewSwaggerDocument_Definitions_Responses(t *testing.T) { initTest() highDoc := NewSwaggerDocument(doc) - assert.Len(t, highDoc.Responses.Definitions, 2) + assert.Equal(t, 2, orderedmap.Len(highDoc.Responses.Definitions)) defs := highDoc.Responses.Definitions - assert.Equal(t, "morning", defs["200"].Extensions["x-coffee"]) - assert.Equal(t, "OK", defs["200"].Description) + assert.Equal(t, "morning", defs.GetOrZero("200").Extensions["x-coffee"]) + assert.Equal(t, "OK", defs.GetOrZero("200").Description) assert.Equal(t, "a generic API response object", - defs["200"].Schema.Schema().Description) - assert.Len(t, defs["200"].Examples.Values, 3) + defs.GetOrZero("200").Schema.Schema().Description) + assert.Equal(t, 3, orderedmap.Len(defs.GetOrZero("200").Examples.Values)) - exp := defs["200"].Examples.Values["application/json"] + exp := defs.GetOrZero("200").Examples.Values.GetOrZero("application/json") assert.Len(t, exp.(map[string]interface{}), 2) assert.Equal(t, "two", exp.(map[string]interface{})["one"]) - exp = defs["200"].Examples.Values["text/xml"] + exp = defs.GetOrZero("200").Examples.Values.GetOrZero("text/xml") assert.Len(t, exp.([]interface{}), 3) assert.Equal(t, "two", exp.([]interface{})[1]) - exp = defs["200"].Examples.Values["text/plain"] + exp = defs.GetOrZero("200").Examples.Values.GetOrZero("text/plain") assert.Equal(t, "something else.", exp) - expWentLow := defs["200"].Examples.GoLow() + expWentLow := defs.GetOrZero("200").Examples.GoLow() assert.Equal(t, 702, expWentLow.FindExample("application/json").ValueNode.Line) assert.Equal(t, 9, expWentLow.FindExample("application/json").ValueNode.Column) wentLow := highDoc.Responses.GoLow() assert.Equal(t, 669, wentLow.FindResponse("200").ValueNode.Line) - y := defs["500"].Headers["someHeader"] + y := defs.GetOrZero("500").Headers.GetOrZero("someHeader") assert.Len(t, y.Enum, 2) x := y.Items @@ -192,7 +193,7 @@ func TestNewSwaggerDocument_Definitions(t *testing.T) { initTest() highDoc := NewSwaggerDocument(doc) - assert.Len(t, highDoc.Definitions.Definitions, 6) + assert.Equal(t, 6, orderedmap.Len(highDoc.Definitions.Definitions)) wentLow := highDoc.Definitions.GoLow() assert.Equal(t, 848, wentLow.FindSchema("User").ValueNode.Line) @@ -202,7 +203,7 @@ func TestNewSwaggerDocument_Definitions(t *testing.T) { func TestNewSwaggerDocument_Paths(t *testing.T) { initTest() highDoc := NewSwaggerDocument(doc) - assert.Len(t, highDoc.Paths.PathItems, 15) + assert.Equal(t, 15, orderedmap.Len(highDoc.Paths.PathItems)) upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage") assert.Equal(t, "man", upload.Extensions["x-potato"]) @@ -264,9 +265,9 @@ func TestNewSwaggerDocument_Responses(t *testing.T) { highDoc := NewSwaggerDocument(doc) upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage").Post - assert.Len(t, upload.Responses.Codes, 1) + assert.Equal(t, 1, orderedmap.Len(upload.Responses.Codes)) - OK := upload.Responses.Codes["200"] + OK := upload.Responses.Codes.GetOrZero("200") assert.Equal(t, "successful operation", OK.Description) assert.Equal(t, "a generic API response object", OK.Schema.Schema().Description) diff --git a/datamodel/high/v3/asyncresult.go b/datamodel/high/v3/asyncresult.go new file mode 100644 index 0000000..caa5340 --- /dev/null +++ b/datamodel/high/v3/asyncresult.go @@ -0,0 +1,6 @@ +package v3 + +type asyncResult[T any] struct { + key string + result T +} diff --git a/datamodel/high/v3/callback.go b/datamodel/high/v3/callback.go index 81b0fc7..7bacd31 100644 --- a/datamodel/high/v3/callback.go +++ b/datamodel/high/v3/callback.go @@ -4,11 +4,13 @@ package v3 import ( + "sort" + "github.com/pb33f/libopenapi/datamodel/high" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" ) // Callback represents a high-level Callback object for OpenAPI 3+. @@ -19,8 +21,8 @@ import ( // that identifies a URL to use for the callback operation. // - https://spec.openapis.org/oas/v3.1.0#callback-object type Callback struct { - Expression map[string]*PathItem `json:"-" yaml:"-"` - Extensions map[string]any `json:"-" yaml:"-"` + Expression orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Callback } @@ -28,9 +30,9 @@ type Callback struct { func NewCallback(lowCallback *low.Callback) *Callback { n := new(Callback) n.low = lowCallback - n.Expression = make(map[string]*PathItem) - for i := range lowCallback.Expression.Value { - n.Expression[i.Value] = NewPathItem(lowCallback.Expression.Value[i].Value) + n.Expression = orderedmap.New[string, *PathItem]() + for pair := orderedmap.First(lowCallback.Expression.Value); pair != nil; pair = pair.Next() { + n.Expression.Set(pair.Key().Value, NewPathItem(pair.Value().Value)) } n.Extensions = make(map[string]any) for k, v := range lowCallback.Extensions { @@ -66,16 +68,16 @@ func (c *Callback) MarshalYAML() (interface{}, error) { } var mapped []*cbItem - for k, ex := range c.Expression { + for pair := orderedmap.First(c.Expression); pair != nil; pair = pair.Next() { ln := 999 // default to a high value to weight new content to the bottom. if c.low != nil { - for lKey := range c.low.Expression.Value { - if lKey.Value == k { - ln = lKey.KeyNode.Line + for lPair := orderedmap.First(c.low.Expression.Value); lPair != nil; lPair = lPair.Next() { + if lPair.Key().Value == pair.Key() { + ln = lPair.Key().KeyNode.Line } } } - mapped = append(mapped, &cbItem{ex, k, ln, nil}) + mapped = append(mapped, &cbItem{pair.Value(), pair.Key(), ln, nil}) } // extract extensions diff --git a/datamodel/high/v3/callback_test.go b/datamodel/high/v3/callback_test.go index b6a748b..e00f1bc 100644 --- a/datamodel/high/v3/callback_test.go +++ b/datamodel/high/v3/callback_test.go @@ -4,19 +4,21 @@ package v3 import ( + "strings" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" v3 "github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "strings" - "testing" ) func TestCallback_MarshalYAML(t *testing.T) { cb := &Callback{ - Expression: map[string]*PathItem{ + Expression: orderedmap.ToOrderedMap(map[string]*PathItem{ "https://pb33f.io": { Get: &Operation{ OperationId: "oneTwoThree", @@ -27,7 +29,7 @@ func TestCallback_MarshalYAML(t *testing.T) { OperationId: "openaypeeeye", }, }, - }, + }), Extensions: map[string]any{ "x-burgers": "why not?", }, @@ -39,7 +41,7 @@ func TestCallback_MarshalYAML(t *testing.T) { assert.Len(t, rend, 152) // mutate - cb.Expression["https://pb33f.io"].Get.OperationId = "blim-blam" + cb.Expression.GetOrZero("https://pb33f.io").Get.OperationId = "blim-blam" cb.Extensions = map[string]interface{}{"x-burgers": "yes please!"} rend, _ = cb.Render() diff --git a/datamodel/high/v3/components.go b/datamodel/high/v3/components.go index cd9555f..c455d95 100644 --- a/datamodel/high/v3/components.go +++ b/datamodel/high/v3/components.go @@ -6,12 +6,12 @@ package v3 import ( "sync" - "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/high" highbase "github.com/pb33f/libopenapi/datamodel/high/base" lowmodel "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -21,16 +21,16 @@ import ( // will have no effect on the API unless they are explicitly referenced from properties outside the components object. // - https://spec.openapis.org/oas/v3.1.0#components-object type Components struct { - Schemas map[string]*highbase.SchemaProxy `json:"schemas,omitempty" yaml:"schemas,omitempty"` - Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` - Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` - Examples map[string]*highbase.Example `json:"examples,omitempty" yaml:"examples,omitempty"` - RequestBodies map[string]*RequestBody `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` - Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` - SecuritySchemes map[string]*SecurityScheme `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` - Links map[string]*Link `json:"links,omitempty" yaml:"links,omitempty"` - Callbacks map[string]*Callback `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Schemas orderedmap.Map[string, *highbase.SchemaProxy] `json:"schemas,omitempty" yaml:"schemas,omitempty"` + Responses orderedmap.Map[string, *Response] `json:"responses,omitempty" yaml:"responses,omitempty"` + Parameters orderedmap.Map[string, *Parameter] `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Examples orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` + RequestBodies orderedmap.Map[string, *RequestBody] `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` + Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"` + SecuritySchemes orderedmap.Map[string, *SecurityScheme] `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` + Links orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"` + Callbacks orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Components } @@ -43,15 +43,15 @@ func NewComponents(comp *low.Components) *Components { if len(comp.Extensions) > 0 { c.Extensions = high.ExtractExtensions(comp.Extensions) } - cbMap := make(map[string]*Callback) - linkMap := make(map[string]*Link) - responseMap := make(map[string]*Response) - parameterMap := make(map[string]*Parameter) - exampleMap := make(map[string]*highbase.Example) - requestBodyMap := make(map[string]*RequestBody) - headerMap := make(map[string]*Header) - securitySchemeMap := make(map[string]*SecurityScheme) - schemas := make(map[string]*highbase.SchemaProxy) + cbMap := orderedmap.New[string, *Callback]() + linkMap := orderedmap.New[string, *Link]() + responseMap := orderedmap.New[string, *Response]() + parameterMap := orderedmap.New[string, *Parameter]() + exampleMap := orderedmap.New[string, *highbase.Example]() + requestBodyMap := orderedmap.New[string, *RequestBody]() + headerMap := orderedmap.New[string, *Header]() + securitySchemeMap := orderedmap.New[string, *SecurityScheme]() + schemas := orderedmap.New[string, *highbase.SchemaProxy]() // build all components asynchronously. var wg sync.WaitGroup @@ -114,32 +114,33 @@ type componentResult[T any] struct { } // buildComponent builds component structs from low level structs. -func buildComponent[IN any, OUT any](inMap map[lowmodel.KeyReference[string]]lowmodel.ValueReference[IN], outMap map[string]OUT, translateItem func(IN) OUT) { - translateFunc := func(key lowmodel.KeyReference[string], value lowmodel.ValueReference[IN]) (componentResult[OUT], error) { - return componentResult[OUT]{key: key.Value, res: translateItem(value.Value)}, nil +func buildComponent[IN any, OUT any](inMap orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]], outMap orderedmap.Map[string, OUT], translateItem func(IN) OUT) { + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[IN]]) (componentResult[OUT], error) { + return componentResult[OUT]{key: pair.Key().Value, res: translateItem(pair.Value().Value)}, nil } resultFunc := func(value componentResult[OUT]) error { - outMap[value.key] = value.res + outMap.Set(value.key, value.res) return nil } - _ = datamodel.TranslateMapParallel(inMap, translateFunc, resultFunc) + _ = orderedmap.TranslateMapParallel(inMap, translateFunc, resultFunc) } // buildSchema builds a schema from low level structs. -func buildSchema(inMap map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*base.SchemaProxy], outMap map[string]*highbase.SchemaProxy) { - translateFunc := func(key lowmodel.KeyReference[string], value lowmodel.ValueReference[*base.SchemaProxy]) (componentResult[*highbase.SchemaProxy], error) { +func buildSchema(inMap orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]], outMap orderedmap.Map[string, *highbase.SchemaProxy]) { + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*base.SchemaProxy]]) (componentResult[*highbase.SchemaProxy], error) { + value := pair.Value() var sch *highbase.SchemaProxy sch = highbase.NewSchemaProxy(&lowmodel.NodeReference[*base.SchemaProxy]{ Value: value.Value, ValueNode: value.ValueNode, }) - return componentResult[*highbase.SchemaProxy]{res: sch, key: key.Value}, nil + return componentResult[*highbase.SchemaProxy]{res: sch, key: pair.Key().Value}, nil } resultFunc := func(value componentResult[*highbase.SchemaProxy]) error { - outMap[value.key] = value.res + outMap.Set(value.key, value.res) return nil } - _ = datamodel.TranslateMapParallel(inMap, translateFunc, resultFunc) + _ = orderedmap.TranslateMapParallel(inMap, translateFunc, resultFunc) } // GoLow returns the low-level Components instance used to create the high-level one. diff --git a/datamodel/high/v3/components_test.go b/datamodel/high/v3/components_test.go index 5be13d4..b925c41 100644 --- a/datamodel/high/v3/components_test.go +++ b/datamodel/high/v3/components_test.go @@ -4,38 +4,40 @@ package v3 import ( + "strings" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" v3 "github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "strings" - "testing" ) func TestComponents_MarshalYAML(t *testing.T) { comp := &Components{ - Responses: map[string]*Response{ + Responses: orderedmap.ToOrderedMap(map[string]*Response{ "200": { Description: "OK", }, - }, - Parameters: map[string]*Parameter{ + }), + Parameters: orderedmap.ToOrderedMap(map[string]*Parameter{ "id": { Name: "id", In: "path", }, - }, - RequestBodies: map[string]*RequestBody{ + }), + RequestBodies: orderedmap.ToOrderedMap(map[string]*RequestBody{ "body": { - Content: map[string]*MediaType{ + Content: orderedmap.ToOrderedMap(map[string]*MediaType{ "application/json": { Example: "why?", }, - }, + }), }, - }, + }), } dat, _ := comp.Render() diff --git a/datamodel/high/v3/document.go b/datamodel/high/v3/document.go index e50c5e5..d3806d5 100644 --- a/datamodel/high/v3/document.go +++ b/datamodel/high/v3/document.go @@ -11,10 +11,12 @@ package v3 import ( "bytes" + "github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high/base" low "github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -81,7 +83,7 @@ type Document struct { // for example by an out-of-band registration. The key name is a unique string to refer to each webhook, // while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider // and the expected responses. An example is available. - Webhooks map[string]*PathItem `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` + Webhooks orderedmap.Map[string, *PathItem] `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` // Index is a reference to the *index.SpecIndex that was created for the document and used // as a guide when building out the Document. Ideal if further processing is required on the model and @@ -129,9 +131,9 @@ func NewDocument(document *low.Document) *Document { d.JsonSchemaDialect = document.JsonSchemaDialect.Value } if !document.Webhooks.IsEmpty() { - hooks := make(map[string]*PathItem) - for h := range document.Webhooks.Value { - hooks[h.Value] = NewPathItem(document.Webhooks.Value[h].Value) + hooks := orderedmap.New[string, *PathItem]() + for pair := orderedmap.First(document.Webhooks.Value); pair != nil; pair = pair.Next() { + hooks.Set(pair.Key().Value, NewPathItem(pair.Value().Value)) } d.Webhooks = hooks } diff --git a/datamodel/high/v3/document_test.go b/datamodel/high/v3/document_test.go index d394b7b..0f6d507 100644 --- a/datamodel/high/v3/document_test.go +++ b/datamodel/high/v3/document_test.go @@ -57,7 +57,7 @@ func TestNewDocument_Security(t *testing.T) { h := NewDocument(lowDoc) assert.Len(t, h.Security, 1) assert.Len(t, h.Security[0].Requirements, 1) - assert.Len(t, h.Security[0].Requirements["OAuthScheme"], 2) + assert.Len(t, h.Security[0].Requirements.GetOrZero("OAuthScheme"), 2) } func TestNewDocument_Info(t *testing.T) { @@ -98,14 +98,14 @@ func TestNewDocument_Servers(t *testing.T) { assert.Equal(t, "{scheme}://api.pb33f.io", h.Servers[0].URL) assert.Equal(t, "this is our main API server, for all fun API things.", h.Servers[0].Description) assert.Len(t, h.Servers[0].Variables, 1) - assert.Equal(t, "https", h.Servers[0].Variables["scheme"].Default) - assert.Len(t, h.Servers[0].Variables["scheme"].Enum, 2) + assert.Equal(t, "https", h.Servers[0].Variables.GetOrZero("scheme").Default) + assert.Len(t, h.Servers[0].Variables.GetOrZero("scheme").Enum, 2) assert.Equal(t, "https://{domain}.{host}.com", h.Servers[1].URL) assert.Equal(t, "this is our second API server, for all fun API things.", h.Servers[1].Description) assert.Len(t, h.Servers[1].Variables, 2) - assert.Equal(t, "api", h.Servers[1].Variables["domain"].Default) - assert.Equal(t, "pb33f.io", h.Servers[1].Variables["host"].Default) + assert.Equal(t, "api", h.Servers[1].Variables.GetOrZero("domain").Default) + assert.Equal(t, "pb33f.io", h.Servers[1].Variables.GetOrZero("host").Default) wentLow := h.GoLow() assert.Equal(t, 45, wentLow.Servers.Value[0].Value.Description.KeyNode.Line) @@ -117,7 +117,7 @@ func TestNewDocument_Servers(t *testing.T) { assert.Equal(t, 45, wentLower.Description.ValueNode.Line) assert.Equal(t, 18, wentLower.Description.ValueNode.Column) - wentLowest := h.Servers[0].Variables["scheme"].GoLow() + wentLowest := h.Servers[0].Variables.GetOrZero("scheme").GoLow() assert.Equal(t, 50, wentLowest.Description.ValueNode.Line) assert.Equal(t, 22, wentLowest.Description.ValueNode.Column) } @@ -147,18 +147,18 @@ func TestNewDocument_Tags(t *testing.T) { func TestNewDocument_Webhooks(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Webhooks, 1) - assert.Equal(t, "Information about a new burger", h.Webhooks["someHook"].Post.RequestBody.Description) + assert.Equal(t, 1, orderedmap.Len(h.Webhooks)) + assert.Equal(t, "Information about a new burger", h.Webhooks.GetOrZero("someHook").Post.RequestBody.Description) } func TestNewDocument_Components_Links(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.Links, 2) - assert.Equal(t, "locateBurger", h.Components.Links["LocateBurger"].OperationId) - assert.Equal(t, "$response.body#/id", h.Components.Links["LocateBurger"].Parameters["burgerId"]) + assert.Equal(t, 2, orderedmap.Len(h.Components.Links)) + assert.Equal(t, "locateBurger", h.Components.Links.GetOrZero("LocateBurger").OperationId) + assert.Equal(t, "$response.body#/id", h.Components.Links.GetOrZero("LocateBurger").Parameters.GetOrZero("burgerId")) - wentLow := h.Components.Links["LocateBurger"].GoLow() + wentLow := h.Components.Links.GetOrZero("LocateBurger").GoLow() assert.Equal(t, 310, wentLow.OperationId.ValueNode.Line) assert.Equal(t, 20, wentLow.OperationId.ValueNode.Column) } @@ -166,29 +166,29 @@ func TestNewDocument_Components_Links(t *testing.T) { func TestNewDocument_Components_Callbacks(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.Callbacks, 1) + assert.Equal(t, 1, orderedmap.Len(h.Components.Callbacks)) assert.Equal( t, "Callback payload", - h.Components.Callbacks["BurgerCallback"].Expression["{$request.query.queryUrl}"].Post.RequestBody.Description, + h.Components.Callbacks.GetOrZero("BurgerCallback").Expression.GetOrZero("{$request.query.queryUrl}").Post.RequestBody.Description, ) assert.Equal( t, 298, - h.Components.Callbacks["BurgerCallback"].GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Line, + h.Components.Callbacks.GetOrZero("BurgerCallback").GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Line, ) assert.Equal( t, 9, - h.Components.Callbacks["BurgerCallback"].GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Column, + h.Components.Callbacks.GetOrZero("BurgerCallback").GoLow().FindExpression("{$request.query.queryUrl}").ValueNode.Column, ) - assert.Equal(t, "please", h.Components.Callbacks["BurgerCallback"].Extensions["x-break-everything"]) + assert.Equal(t, "please", h.Components.Callbacks.GetOrZero("BurgerCallback").Extensions["x-break-everything"]) - for k := range h.Components.GoLow().Callbacks.Value { - if k.Value == "BurgerCallback" { - assert.Equal(t, 295, k.KeyNode.Line) - assert.Equal(t, 5, k.KeyNode.Column) + for pair := orderedmap.First(h.Components.GoLow().Callbacks.Value); pair != nil; pair = pair.Next() { + if pair.Key().Value == "BurgerCallback" { + assert.Equal(t, 295, pair.Key().KeyNode.Line) + assert.Equal(t, 5, pair.Key().KeyNode.Column) } } } @@ -196,30 +196,30 @@ func TestNewDocument_Components_Callbacks(t *testing.T) { func TestNewDocument_Components_Schemas(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.Schemas, 6) + assert.Equal(t, 6, orderedmap.Len(h.Components.Schemas)) goLow := h.Components.GoLow() - a := h.Components.Schemas["Error"] - abcd := a.Schema().Properties["message"].Schema().Example + a := h.Components.Schemas.GetOrZero("Error") + abcd := a.Schema().Properties.GetOrZero("message").Schema().Example assert.Equal(t, "No such burger as 'Big-Whopper'", abcd) assert.Equal(t, 433, goLow.Schemas.KeyNode.Line) assert.Equal(t, 3, goLow.Schemas.KeyNode.Column) assert.Equal(t, 436, a.Schema().GoLow().Description.KeyNode.Line) - b := h.Components.Schemas["Burger"] + b := h.Components.Schemas.GetOrZero("Burger") assert.Len(t, b.Schema().Required, 2) - assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties["fries"].Schema().Description) - assert.Equal(t, int64(2), b.Schema().Properties["numPatties"].Schema().Example) + assert.Equal(t, "golden slices of happy fun joy", b.Schema().Properties.GetOrZero("fries").Schema().Description) + assert.Equal(t, int64(2), b.Schema().Properties.GetOrZero("numPatties").Schema().Example) assert.Equal(t, 448, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Line) assert.Equal(t, 7, goLow.FindSchema("Burger").Value.Schema().Properties.KeyNode.Column) assert.Equal(t, 450, b.Schema().GoLow().FindProperty("name").ValueNode.Line) - f := h.Components.Schemas["Fries"] - 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, 1) + f := h.Components.Schemas.GetOrZero("Fries") + assert.Equal(t, "salt", f.Schema().Properties.GetOrZero("seasoning").Schema().Items.A.Schema().Example) + assert.Len(t, f.Schema().Properties.GetOrZero("favoriteDrink").Schema().Properties.GetOrZero("drinkType").Schema().Enum, 1) - d := h.Components.Schemas["Drink"] + d := h.Components.Schemas.GetOrZero("Drink") assert.Len(t, d.Schema().Required, 2) assert.True(t, d.Schema().AdditionalProperties.(bool)) assert.Equal(t, "drinkType", d.Schema().Discriminator.PropertyName) @@ -227,7 +227,7 @@ func TestNewDocument_Components_Schemas(t *testing.T) { assert.Equal(t, 516, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Line) assert.Equal(t, 23, d.Schema().Discriminator.GoLow().PropertyName.ValueNode.Column) - pl := h.Components.Schemas["SomePayload"] + pl := h.Components.Schemas.GetOrZero("SomePayload") assert.Equal(t, "is html programming? yes.", pl.Schema().XML.Name) assert.Equal(t, 523, pl.Schema().XML.GoLow().Name.ValueNode.Line) @@ -238,62 +238,62 @@ func TestNewDocument_Components_Schemas(t *testing.T) { func TestNewDocument_Components_Headers(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.Headers, 1) - assert.Equal(t, "this is a header example for UseOil", h.Components.Headers["UseOil"].Description) - assert.Equal(t, 323, h.Components.Headers["UseOil"].GoLow().Description.ValueNode.Line) - assert.Equal(t, 20, h.Components.Headers["UseOil"].GoLow().Description.ValueNode.Column) + assert.Equal(t, 1, orderedmap.Len(h.Components.Headers)) + assert.Equal(t, "this is a header example for UseOil", h.Components.Headers.GetOrZero("UseOil").Description) + assert.Equal(t, 323, h.Components.Headers.GetOrZero("UseOil").GoLow().Description.ValueNode.Line) + assert.Equal(t, 20, h.Components.Headers.GetOrZero("UseOil").GoLow().Description.ValueNode.Column) } func TestNewDocument_Components_RequestBodies(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.RequestBodies, 1) - assert.Equal(t, "Give us the new burger!", h.Components.RequestBodies["BurgerRequest"].Description) - assert.Equal(t, 328, h.Components.RequestBodies["BurgerRequest"].GoLow().Description.ValueNode.Line) - assert.Equal(t, 20, h.Components.RequestBodies["BurgerRequest"].GoLow().Description.ValueNode.Column) - assert.Len(t, h.Components.RequestBodies["BurgerRequest"].Content["application/json"].Examples, 2) + assert.Equal(t, 1, orderedmap.Len(h.Components.RequestBodies)) + assert.Equal(t, "Give us the new burger!", h.Components.RequestBodies.GetOrZero("BurgerRequest").Description) + assert.Equal(t, 328, h.Components.RequestBodies.GetOrZero("BurgerRequest").GoLow().Description.ValueNode.Line) + assert.Equal(t, 20, h.Components.RequestBodies.GetOrZero("BurgerRequest").GoLow().Description.ValueNode.Column) + assert.Equal(t, 2, orderedmap.Len(h.Components.RequestBodies.GetOrZero("BurgerRequest").Content.GetOrZero("application/json").Examples)) } func TestNewDocument_Components_Examples(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.Examples, 1) - assert.Equal(t, "A juicy two hander sammich", h.Components.Examples["QuarterPounder"].Summary) - assert.Equal(t, 346, h.Components.Examples["QuarterPounder"].GoLow().Summary.ValueNode.Line) - assert.Equal(t, 16, h.Components.Examples["QuarterPounder"].GoLow().Summary.ValueNode.Column) + assert.Equal(t, 1, orderedmap.Len(h.Components.Examples)) + assert.Equal(t, "A juicy two hander sammich", h.Components.Examples.GetOrZero("QuarterPounder").Summary) + assert.Equal(t, 346, h.Components.Examples.GetOrZero("QuarterPounder").GoLow().Summary.ValueNode.Line) + assert.Equal(t, 16, h.Components.Examples.GetOrZero("QuarterPounder").GoLow().Summary.ValueNode.Column) } func TestNewDocument_Components_Responses(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.Responses, 1) - assert.Equal(t, "all the dressings for a burger.", h.Components.Responses["DressingResponse"].Description) - assert.Equal(t, "array", h.Components.Responses["DressingResponse"].Content["application/json"].Schema.Schema().Type[0]) - assert.Equal(t, 352, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Line) - assert.Equal(t, 7, h.Components.Responses["DressingResponse"].GoLow().Description.KeyNode.Column) + assert.Equal(t, 1, orderedmap.Len(h.Components.Responses)) + assert.Equal(t, "all the dressings for a burger.", h.Components.Responses.GetOrZero("DressingResponse").Description) + assert.Equal(t, "array", h.Components.Responses.GetOrZero("DressingResponse").Content.GetOrZero("application/json").Schema.Schema().Type[0]) + assert.Equal(t, 352, h.Components.Responses.GetOrZero("DressingResponse").GoLow().Description.KeyNode.Line) + assert.Equal(t, 7, h.Components.Responses.GetOrZero("DressingResponse").GoLow().Description.KeyNode.Column) } func TestNewDocument_Components_SecuritySchemes(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.SecuritySchemes, 3) + assert.Equal(t, 3, orderedmap.Len(h.Components.SecuritySchemes)) - api := h.Components.SecuritySchemes["APIKeyScheme"] + api := h.Components.SecuritySchemes.GetOrZero("APIKeyScheme") assert.Equal(t, "an apiKey security scheme", api.Description) assert.Equal(t, 364, api.GoLow().Description.ValueNode.Line) assert.Equal(t, 20, api.GoLow().Description.ValueNode.Column) - jwt := h.Components.SecuritySchemes["JWTScheme"] + jwt := h.Components.SecuritySchemes.GetOrZero("JWTScheme") assert.Equal(t, "an JWT security scheme", jwt.Description) assert.Equal(t, 369, jwt.GoLow().Description.ValueNode.Line) assert.Equal(t, 20, jwt.GoLow().Description.ValueNode.Column) - oAuth := h.Components.SecuritySchemes["OAuthScheme"] + oAuth := h.Components.SecuritySchemes.GetOrZero("OAuthScheme") 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.Len(t, oAuth.Flows.Implicit.Scopes, 2) - assert.Equal(t, "read all burgers", oAuth.Flows.Implicit.Scopes["read:burgers"]) + assert.Equal(t, 2, orderedmap.Len(oAuth.Flows.Implicit.Scopes)) + 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. @@ -306,26 +306,26 @@ func TestNewDocument_Components_SecuritySchemes(t *testing.T) { func TestNewDocument_Components_Parameters(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Components.Parameters, 2) - bh := h.Components.Parameters["BurgerHeader"] + assert.Equal(t, 2, orderedmap.Len(h.Components.Parameters)) + bh := h.Components.Parameters.GetOrZero("BurgerHeader") assert.Equal(t, "burgerHeader", bh.Name) assert.Equal(t, 392, bh.GoLow().Name.KeyNode.Line) - assert.Len(t, bh.Schema.Schema().Properties, 2) + assert.Equal(t, 2, orderedmap.Len(bh.Schema.Schema().Properties)) assert.Equal(t, "big-mac", bh.Example) assert.True(t, bh.Required) assert.Equal( t, "this is a header", - bh.Content["application/json"].Encoding["burgerTheme"].Headers["someHeader"].Description, + bh.Content.GetOrZero("application/json").Encoding.GetOrZero("burgerTheme").Headers.GetOrZero("someHeader").Description, ) - assert.Len(t, bh.Content["application/json"].Schema.Schema().Properties, 2) - assert.Equal(t, 409, bh.Content["application/json"].Encoding["burgerTheme"].GoLow().ContentType.ValueNode.Line) + assert.Equal(t, 2, orderedmap.Len(bh.Content.GetOrZero("application/json").Schema.Schema().Properties)) + assert.Equal(t, 409, bh.Content.GetOrZero("application/json").Encoding.GetOrZero("burgerTheme").GoLow().ContentType.ValueNode.Line) } func TestNewDocument_Paths(t *testing.T) { initTest() h := NewDocument(lowDoc) - assert.Len(t, h.Paths.PathItems, 5) + assert.Equal(t, 5, orderedmap.Len(h.Paths.PathItems)) testBurgerShop(t, h, true) } @@ -346,36 +346,36 @@ func testBurgerShop(t *testing.T, h *Document, checkLines bool) { assert.Len(t, burgersOp.Post.Tags, 1) assert.Equal(t, "A new burger for our menu, yummy yum yum.", burgersOp.Post.Description) assert.Equal(t, "Give us the new burger!", burgersOp.Post.RequestBody.Description) - assert.Len(t, burgersOp.Post.Responses.Codes, 3) + assert.Equal(t, 3, orderedmap.Len(burgersOp.Post.Responses.Codes)) if checkLines { assert.Equal(t, 64, burgersOp.GoLow().Post.KeyNode.Line) assert.Equal(t, 63, h.Paths.GoLow().FindPath("/burgers").ValueNode.Line) } okResp := burgersOp.Post.Responses.FindResponseByCode(200) - assert.Len(t, okResp.Headers, 1) + assert.Equal(t, 1, orderedmap.Len(okResp.Headers)) assert.Equal(t, "A tasty burger for you to eat.", okResp.Description) - assert.Len(t, okResp.Content["application/json"].Examples, 2) + assert.Equal(t, 2, orderedmap.Len(okResp.Content.GetOrZero("application/json").Examples)) assert.Equal( t, "a cripsy fish sammich filled with ocean goodness.", - okResp.Content["application/json"].Examples["filetOFish"].Summary, + okResp.Content.GetOrZero("application/json").Examples.GetOrZero("filetOFish").Summary, ) - assert.Len(t, okResp.Links, 2) - assert.Equal(t, "locateBurger", okResp.Links["LocateBurger"].OperationId) + assert.Equal(t, 2, orderedmap.Len(okResp.Links)) + assert.Equal(t, "locateBurger", okResp.Links.GetOrZero("LocateBurger").OperationId) assert.Len(t, burgersOp.Post.Security[0].Requirements, 1) - assert.Len(t, burgersOp.Post.Security[0].Requirements["OAuthScheme"], 2) - assert.Equal(t, "read:burgers", burgersOp.Post.Security[0].Requirements["OAuthScheme"][0]) + assert.Len(t, burgersOp.Post.Security[0].Requirements.GetOrZero("OAuthScheme"), 2) + assert.Equal(t, "read:burgers", burgersOp.Post.Security[0].Requirements.GetOrZero("OAuthScheme")[0]) assert.Len(t, burgersOp.Post.Servers, 1) assert.Equal(t, "https://pb33f.io", burgersOp.Post.Servers[0].URL) if checkLines { assert.Equal(t, 69, burgersOp.Post.GoLow().Description.ValueNode.Line) assert.Equal(t, 74, burgersOp.Post.Responses.GoLow().FindResponseByCode("200").ValueNode.Line) - assert.Equal(t, 80, okResp.Content["application/json"].GoLow().Schema.KeyNode.Line) - assert.Equal(t, 15, okResp.Content["application/json"].GoLow().Schema.KeyNode.Column) + assert.Equal(t, 80, okResp.Content.GetOrZero("application/json").GoLow().Schema.KeyNode.Line) + assert.Equal(t, 15, okResp.Content.GetOrZero("application/json").GoLow().Schema.KeyNode.Column) assert.Equal(t, 77, okResp.GoLow().Description.KeyNode.Line) - assert.Equal(t, 310, okResp.Links["LocateBurger"].GoLow().OperationId.ValueNode.Line) + assert.Equal(t, 310, okResp.Links.GetOrZero("LocateBurger").GoLow().OperationId.ValueNode.Line) assert.Equal(t, 118, burgersOp.Post.Security[0].GoLow().Requirements.ValueNode.Line) } @@ -534,7 +534,7 @@ func TestDocument_MarshalJSON(t *testing.T) { newDoc := NewDocument(lowDoc) assert.Equal(t, orderedmap.Len(newDoc.Paths.PathItems), orderedmap.Len(highDoc.Paths.PathItems)) - assert.Equal(t, len(newDoc.Components.Schemas), len(highDoc.Components.Schemas)) + assert.Equal(t, orderedmap.Len(newDoc.Components.Schemas), orderedmap.Len(highDoc.Components.Schemas)) } func TestDocument_MarshalYAMLInline(t *testing.T) { @@ -711,8 +711,8 @@ components: h := NewDocument(lowDoc) // mutate the schema - g := h.Components.Schemas["BurgerHeader"].Schema() - ds := g.Properties["burgerTheme"].Schema() + g := h.Components.Schemas.GetOrZero("BurgerHeader").Schema() + ds := g.Properties.GetOrZero("burgerTheme").Schema() ds.Description = "changed" // render the document to YAML and it should be identical. diff --git a/datamodel/high/v3/encoding.go b/datamodel/high/v3/encoding.go index e4c348b..fd8d792 100644 --- a/datamodel/high/v3/encoding.go +++ b/datamodel/high/v3/encoding.go @@ -7,17 +7,18 @@ import ( "github.com/pb33f/libopenapi/datamodel/high" lowmodel "github.com/pb33f/libopenapi/datamodel/low" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) // Encoding represents an OpenAPI 3+ Encoding object // - https://spec.openapis.org/oas/v3.1.0#encoding-object type Encoding struct { - ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` - Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` - Style string `json:"style,omitempty" yaml:"style,omitempty"` - Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` - AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` + ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` + Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` low *low.Encoding } @@ -55,10 +56,10 @@ func (e *Encoding) MarshalYAML() (interface{}, error) { } // ExtractEncoding converts hard to navigate low-level plumbing Encoding definitions, into a high-level simple map -func ExtractEncoding(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Encoding]) map[string]*Encoding { - extracted := make(map[string]*Encoding) - for k, v := range elements { - extracted[k.Value] = NewEncoding(v.Value) +func ExtractEncoding(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Encoding]]) orderedmap.Map[string, *Encoding] { + extracted := orderedmap.New[string, *Encoding]() + for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() { + extracted.Set(pair.Key().Value, NewEncoding(pair.Value().Value)) } return extracted } diff --git a/datamodel/high/v3/encoding_test.go b/datamodel/high/v3/encoding_test.go index 56c2698..8a0fef6 100644 --- a/datamodel/high/v3/encoding_test.go +++ b/datamodel/high/v3/encoding_test.go @@ -4,9 +4,11 @@ package v3 import ( - "github.com/stretchr/testify/assert" "strings" "testing" + + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" ) func TestEncoding_MarshalYAML(t *testing.T) { @@ -14,9 +16,11 @@ func TestEncoding_MarshalYAML(t *testing.T) { explode := true encoding := &Encoding{ ContentType: "application/json", - Headers: map[string]*Header{"x-pizza-time": {Description: "oh yes please"}}, - Style: "simple", - Explode: &explode, + Headers: orderedmap.ToOrderedMap(map[string]*Header{ + "x-pizza-time": {Description: "oh yes please"}, + }), + Style: "simple", + Explode: &explode, } rend, _ := encoding.Render() diff --git a/datamodel/high/v3/header.go b/datamodel/high/v3/header.go index 9dc0bf9..39057c2 100644 --- a/datamodel/high/v3/header.go +++ b/datamodel/high/v3/header.go @@ -9,24 +9,25 @@ import ( lowmodel "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) // Header represents a high-level OpenAPI 3+ Header object that is backed by a low-level one. // - https://spec.openapis.org/oas/v3.1.0#header-object type Header struct { - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Required bool `json:"required,omitempty" yaml:"required,omitempty"` - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` - Style string `json:"style,omitempty" yaml:"style,omitempty"` - Explode bool `json:"explode,omitempty" yaml:"explode,omitempty"` - AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` - Schema *highbase.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` - Example any `json:"example,omitempty" yaml:"example,omitempty"` - Examples map[string]*highbase.Example `json:"examples,omitempty" yaml:"examples,omitempty"` - Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` + Schema *highbase.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Examples orderedmap.Map[string, *highbase.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` + Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Header } @@ -65,10 +66,10 @@ func (h *Header) GoLowUntyped() any { } // ExtractHeaders will extract a hard to navigate low-level Header map, into simple high-level one. -func ExtractHeaders(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Header]) map[string]*Header { - extracted := make(map[string]*Header) - for k, v := range elements { - extracted[k.Value] = NewHeader(v.Value) +func ExtractHeaders(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.Header]]) orderedmap.Map[string, *Header] { + extracted := orderedmap.New[string, *Header]() + for pair := orderedmap.First(elements); pair != nil; pair = pair.Next() { + extracted.Set(pair.Key().Value, NewHeader(pair.Value().Value)) } return extracted } diff --git a/datamodel/high/v3/header_test.go b/datamodel/high/v3/header_test.go index 5cb4c6a..13ff895 100644 --- a/datamodel/high/v3/header_test.go +++ b/datamodel/high/v3/header_test.go @@ -4,10 +4,12 @@ package v3 import ( - "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/stretchr/testify/assert" "strings" "testing" + + "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" ) func TestHeader_MarshalYAML(t *testing.T) { @@ -21,8 +23,10 @@ func TestHeader_MarshalYAML(t *testing.T) { Explode: true, AllowReserved: true, Example: "example", - Examples: map[string]*base.Example{"example": {Value: "example"}}, - Extensions: map[string]interface{}{"x-burgers": "why not?"}, + Examples: orderedmap.ToOrderedMap(map[string]*base.Example{ + "example": {Value: "example"}, + }), + Extensions: map[string]interface{}{"x-burgers": "why not?"}, } rend, _ := header.Render() diff --git a/datamodel/high/v3/link.go b/datamodel/high/v3/link.go index d414872..07917d7 100644 --- a/datamodel/high/v3/link.go +++ b/datamodel/high/v3/link.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" ) @@ -22,13 +23,13 @@ import ( // in an operation and using them as parameters while invoking the linked operation. // - https://spec.openapis.org/oas/v3.1.0#link-object type Link struct { - OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` - OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` - Parameters map[string]string `json:"parameters,omitempty" yaml:"parameters,omitempty"` - RequestBody string `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Server *Server `json:"server,omitempty" yaml:"server,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` + OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + Parameters orderedmap.Map[string, string] `json:"parameters,omitempty" yaml:"parameters,omitempty"` + RequestBody string `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Server *Server `json:"server,omitempty" yaml:"server,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Link } @@ -38,9 +39,9 @@ func NewLink(link *low.Link) *Link { l.low = link l.OperationRef = link.OperationRef.Value l.OperationId = link.OperationId.Value - params := make(map[string]string) - for k, v := range link.Parameters.Value { - params[k.Value] = v.Value + params := orderedmap.New[string, string]() + for pair := orderedmap.First(link.Parameters.Value); pair != nil; pair = pair.Next() { + params.Set(pair.Key().Value, pair.Value().Value) } l.Parameters = params l.RequestBody = link.RequestBody.Value diff --git a/datamodel/high/v3/link_test.go b/datamodel/high/v3/link_test.go index 111db3e..c8eef0b 100644 --- a/datamodel/high/v3/link_test.go +++ b/datamodel/high/v3/link_test.go @@ -4,18 +4,20 @@ package v3 import ( - "github.com/stretchr/testify/assert" "strings" "testing" + + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" ) func TestLink_MarshalYAML(t *testing.T) { link := Link{ OperationRef: "somewhere", OperationId: "somewhereOutThere", - Parameters: map[string]string{ + Parameters: orderedmap.ToOrderedMap(map[string]string{ "over": "theRainbow", - }, + }), RequestBody: "hello?", Description: "are you there?", Server: &Server{ diff --git a/datamodel/high/v3/media_type.go b/datamodel/high/v3/media_type.go index 2a45851..c55af36 100644 --- a/datamodel/high/v3/media_type.go +++ b/datamodel/high/v3/media_type.go @@ -4,12 +4,11 @@ package v3 import ( - "sync" - "github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high/base" lowmodel "github.com/pb33f/libopenapi/datamodel/low" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -18,11 +17,11 @@ import ( // Each Media Type Object provides schema and examples for the media type identified by its key. // - https://spec.openapis.org/oas/v3.1.0#media-type-object type MediaType struct { - Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` - Example any `json:"example,omitempty" yaml:"example,omitempty"` - Examples map[string]*base.Example `json:"examples,omitempty" yaml:"examples,omitempty"` - Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Examples orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` + Encoding orderedmap.Map[string, *Encoding] `json:"encoding,omitempty" yaml:"encoding,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.MediaType } @@ -74,29 +73,18 @@ func (m *MediaType) MarshalYAMLInline() (interface{}, error) { // ExtractContent takes in a complex and hard to navigate low-level content map, and converts it in to a much simpler // and easier to navigate high-level one. -func ExtractContent(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.MediaType]) map[string]*MediaType { - // extract everything async - doneChan := make(chan bool) - - var extLock sync.RWMutex - extractContentItem := func(k lowmodel.KeyReference[string], - v lowmodel.ValueReference[*low.MediaType], c chan bool, e map[string]*MediaType) { - extLock.Lock() - e[k.Value] = NewMediaType(v.Value) - extLock.Unlock() - c <- true - +func ExtractContent(elements orderedmap.Map[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) orderedmap.Map[string, *MediaType] { + extracted := orderedmap.New[string, *MediaType]() + translateFunc := func(pair orderedmap.Pair[lowmodel.KeyReference[string], lowmodel.ValueReference[*low.MediaType]]) (asyncResult[*MediaType], error) { + return asyncResult[*MediaType]{ + key: pair.Key().Value, + result: NewMediaType(pair.Value().Value), + }, nil } - extracted := make(map[string]*MediaType) - for k, v := range elements { - go extractContentItem(k, v, doneChan, extracted) - } - n := 0 - for n < len(elements) { - select { - case <-doneChan: - n++ - } + resultFunc := func(value asyncResult[*MediaType]) error { + extracted.Set(value.key, value.result) + return nil } + _ = orderedmap.TranslateMapParallel(elements, translateFunc, resultFunc) return extracted } diff --git a/datamodel/high/v3/media_type_test.go b/datamodel/high/v3/media_type_test.go index 361dc64..21f2e5f 100644 --- a/datamodel/high/v3/media_type_test.go +++ b/datamodel/high/v3/media_type_test.go @@ -28,7 +28,7 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) { // create a new document and extract a media type object from it. d := NewDocument(lowDoc) - mt := d.Paths.PathItems.GetOrZero("/pet").Put.RequestBody.Content["application/json"] + mt := d.Paths.PathItems.GetOrZero("/pet").Put.RequestBody.Content.GetOrZero("application/json") // render out the media type yml, _ := mt.Render() @@ -118,7 +118,7 @@ func TestMediaType_MarshalYAML(t *testing.T) { // create a new document and extract a media type object from it. d := NewDocument(lowDoc) - mt := d.Paths.PathItems.GetOrZero("/pet").Put.RequestBody.Content["application/json"] + mt := d.Paths.PathItems.GetOrZero("/pet").Put.RequestBody.Content.GetOrZero("application/json") // render out the media type yml, _ := mt.Render() diff --git a/datamodel/high/v3/oauth_flow.go b/datamodel/high/v3/oauth_flow.go index 5aa3d59..316804d 100644 --- a/datamodel/high/v3/oauth_flow.go +++ b/datamodel/high/v3/oauth_flow.go @@ -6,17 +6,18 @@ 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" ) // OAuthFlow represents a high-level OpenAPI 3+ OAuthFlow object that is backed by a low-level one. // - https://spec.openapis.org/oas/v3.1.0#oauth-flow-object 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"` - Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + AuthorizationUrl string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` + TokenUrl string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` + RefreshUrl string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` + Scopes orderedmap.Map[string, string] `json:"scopes,omitempty" yaml:"scopes,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.OAuthFlow } @@ -27,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 := make(map[string]string) - for k, v := range flow.Scopes.Value { - scopes[k.Value] = v.Value + scopes := orderedmap.New[string, string]() + for pair := orderedmap.First(flow.Scopes.Value); 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 f81a00f..bd5dcf1 100644 --- a/datamodel/high/v3/oauth_flow_test.go +++ b/datamodel/high/v3/oauth_flow_test.go @@ -4,9 +4,11 @@ package v3 import ( - "github.com/stretchr/testify/assert" "strings" "testing" + + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" ) func TestOAuthFlow_MarshalYAML(t *testing.T) { @@ -15,7 +17,10 @@ func TestOAuthFlow_MarshalYAML(t *testing.T) { AuthorizationUrl: "https://pb33f.io", TokenUrl: "https://pb33f.io/token", RefreshUrl: "https://pb33f.io/refresh", - Scopes: map[string]string{"chicken": "nuggets", "beefy": "soup"}, + Scopes: orderedmap.ToOrderedMap(map[string]string{ + "chicken": "nuggets", + "beefy": "soup", + }), } rend, _ := oflow.Render() diff --git a/datamodel/high/v3/oauth_flows_test.go b/datamodel/high/v3/oauth_flows_test.go index 42f5229..ac5f16f 100644 --- a/datamodel/high/v3/oauth_flows_test.go +++ b/datamodel/high/v3/oauth_flows_test.go @@ -4,13 +4,14 @@ package v3 import ( + "strings" + "testing" + "github.com/pb33f/libopenapi/datamodel/low" v3 "github.com/pb33f/libopenapi/datamodel/low/v3" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "strings" - "testing" ) func TestNewOAuthFlows(t *testing.T) { @@ -81,7 +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/high/v3/operation.go b/datamodel/high/v3/operation.go index 568900b..b12738e 100644 --- a/datamodel/high/v3/operation.go +++ b/datamodel/high/v3/operation.go @@ -7,6 +7,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high/base" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -16,19 +17,19 @@ import ( // happens here. The entire being for existence of this library and the specification, is this Operation. // - https://spec.openapis.org/oas/v3.1.0#operation-object type Operation struct { - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` - Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - ExternalDocs *base.ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` - OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` - Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` - RequestBody *RequestBody `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` - Responses *Responses `json:"responses,omitempty" yaml:"responses,omitempty"` - Callbacks map[string]*Callback `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` - Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"` - Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + ExternalDocs *base.ExternalDoc `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` + RequestBody *RequestBody `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + Responses *Responses `json:"responses,omitempty" yaml:"responses,omitempty"` + Callbacks orderedmap.Map[string, *Callback] `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` + Deprecated *bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + Security []*base.SecurityRequirement `json:"security,omitempty" yaml:"security,omitempty"` + Servers []*Server `json:"servers,omitempty" yaml:"servers,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Operation } @@ -81,9 +82,9 @@ func NewOperation(operation *low.Operation) *Operation { o.Servers = servers o.Extensions = high.ExtractExtensions(operation.Extensions) if !operation.Callbacks.IsEmpty() { - cbs := make(map[string]*Callback) - for k, v := range operation.Callbacks.Value { - cbs[k.Value] = NewCallback(v.Value) + cbs := orderedmap.New[string, *Callback]() + for pair := orderedmap.First(operation.Callbacks.Value); pair != nil; pair = pair.Next() { + cbs.Set(pair.Key().Value, NewCallback(pair.Value().Value)) } o.Callbacks = cbs } diff --git a/datamodel/high/v3/operation_test.go b/datamodel/high/v3/operation_test.go index 55a68d4..d1982e5 100644 --- a/datamodel/high/v3/operation_test.go +++ b/datamodel/high/v3/operation_test.go @@ -50,7 +50,7 @@ callbacks: assert.Equal(t, "https://pb33f.io", r.ExternalDocs.URL) assert.Equal(t, 1, r.GoLow().ExternalDocs.KeyNode.Line) assert.Contains(t, r.Callbacks, "testCallback") - assert.Contains(t, r.Callbacks["testCallback"].Expression, "{$request.body#/callbackUrl}") + assert.Contains(t, r.Callbacks.GetOrZero("testCallback").Expression, "{$request.body#/callbackUrl}") assert.Equal(t, 3, r.GoLow().Callbacks.KeyNode.Line) } diff --git a/datamodel/high/v3/package_test.go b/datamodel/high/v3/package_test.go index 6c43270..264c6ab 100644 --- a/datamodel/high/v3/package_test.go +++ b/datamodel/high/v3/package_test.go @@ -38,6 +38,6 @@ func Example_createHighLevelOpenAPIDocument() { // Print out some details fmt.Printf("Petstore contains %d paths and %d component schemas", - orderedmap.Len(doc.Paths.PathItems), len(doc.Components.Schemas)) + orderedmap.Len(doc.Paths.PathItems), orderedmap.Len(doc.Components.Schemas)) // Output: Petstore contains 13 paths and 8 component schemas } diff --git a/datamodel/high/v3/parameter.go b/datamodel/high/v3/parameter.go index 57313f2..ed85fbf 100644 --- a/datamodel/high/v3/parameter.go +++ b/datamodel/high/v3/parameter.go @@ -7,6 +7,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high/base" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -15,20 +16,20 @@ import ( // A unique parameter is defined by a combination of a name and location. // - https://spec.openapis.org/oas/v3.1.0#parameter-object type Parameter struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - In string `json:"in,omitempty" yaml:"in,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Required bool `json:"required,omitempty" yaml:"required,omitempty"` - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` - Style string `json:"style,omitempty" yaml:"style,omitempty"` - Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` - AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` - Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` - Example any `json:"example,omitempty" yaml:"example,omitempty"` - Examples map[string]*base.Example `json:"examples,omitempty" yaml:"examples,omitempty"` - Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` + Schema *base.SchemaProxy `json:"schema,omitempty" yaml:"schema,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Examples orderedmap.Map[string, *base.Example] `json:"examples,omitempty" yaml:"examples,omitempty"` + Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Parameter } diff --git a/datamodel/high/v3/parameter_test.go b/datamodel/high/v3/parameter_test.go index a79f3e0..6fe1145 100644 --- a/datamodel/high/v3/parameter_test.go +++ b/datamodel/high/v3/parameter_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" ) @@ -23,8 +24,10 @@ func TestParameter_MarshalYAML(t *testing.T) { Explode: &explode, AllowReserved: true, Example: "example", - Examples: map[string]*base.Example{"example": {Value: "example"}}, - Extensions: map[string]interface{}{"x-burgers": "why not?"}, + Examples: orderedmap.ToOrderedMap(map[string]*base.Example{ + "example": {Value: "example"}, + }), + Extensions: map[string]interface{}{"x-burgers": "why not?"}, } rend, _ := param.Render() @@ -57,8 +60,10 @@ func TestParameter_MarshalYAMLInline(t *testing.T) { Explode: &explode, AllowReserved: true, Example: "example", - Examples: map[string]*base.Example{"example": {Value: "example"}}, - Extensions: map[string]interface{}{"x-burgers": "why not?"}, + Examples: orderedmap.ToOrderedMap(map[string]*base.Example{ + "example": {Value: "example"}, + }), + Extensions: map[string]interface{}{"x-burgers": "why not?"}, } rend, _ := param.RenderInline() diff --git a/datamodel/high/v3/paths.go b/datamodel/high/v3/paths.go index fa0f241..696ba17 100644 --- a/datamodel/high/v3/paths.go +++ b/datamodel/high/v3/paths.go @@ -84,7 +84,7 @@ func (p *Paths) MarshalYAML() (interface{}, error) { } var mapped []*pathItem - action := func(pair orderedmap.Pair[string, *PathItem]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { k := pair.Key() pi := pair.Value() ln := 9999 // default to a high value to weight new content to the bottom. @@ -95,9 +95,7 @@ func (p *Paths) MarshalYAML() (interface{}, error) { } } mapped = append(mapped, &pathItem{pi, k, ln, nil}) - return nil } - _ = orderedmap.For[string, *PathItem](p.PathItems, action) nb := high.NewNodeBuilder(p, p.low) extNode := nb.Render() @@ -142,7 +140,7 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) { } var mapped []*pathItem - action := func(pair orderedmap.Pair[string, *PathItem]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { k := pair.Key() pi := pair.Value() ln := 9999 // default to a high value to weight new content to the bottom. @@ -153,9 +151,7 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) { } } mapped = append(mapped, &pathItem{pi, k, ln, nil}) - return nil } - _ = orderedmap.For[string, *PathItem](p.PathItems, action) nb := high.NewNodeBuilder(p, p.low) nb.Resolve = true diff --git a/datamodel/high/v3/request_body.go b/datamodel/high/v3/request_body.go index 8fee1ad..85c7421 100644 --- a/datamodel/high/v3/request_body.go +++ b/datamodel/high/v3/request_body.go @@ -6,16 +6,17 @@ 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" ) // RequestBody represents a high-level OpenAPI 3+ RequestBody object, backed by a low-level one. // - https://spec.openapis.org/oas/v3.1.0#request-body-object type RequestBody struct { - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"` - Required *bool `json:"required,omitempty" yaml:"required,renderZero,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + Required *bool `json:"required,omitempty" yaml:"required,renderZero,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.RequestBody } diff --git a/datamodel/high/v3/response.go b/datamodel/high/v3/response.go index c2866ca..0cb890a 100644 --- a/datamodel/high/v3/response.go +++ b/datamodel/high/v3/response.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,11 +16,11 @@ import ( // operations based on the response. // - https://spec.openapis.org/oas/v3.1.0#response-object type Response struct { - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` - Content map[string]*MediaType `json:"content,omitempty" yaml:"content,omitempty"` - Links map[string]*Link `json:"links,omitempty" yaml:"links,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Headers orderedmap.Map[string, *Header] `json:"headers,omitempty" yaml:"headers,omitempty"` + Content orderedmap.Map[string, *MediaType] `json:"content,omitempty" yaml:"content,omitempty"` + Links orderedmap.Map[string, *Link] `json:"links,omitempty" yaml:"links,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Response } @@ -36,9 +37,9 @@ func NewResponse(response *low.Response) *Response { r.Content = ExtractContent(response.Content.Value) } if !response.Links.IsEmpty() { - responseLinks := make(map[string]*Link) - for k, v := range response.Links.Value { - responseLinks[k.Value] = NewLink(v.Value) + responseLinks := orderedmap.New[string, *Link]() + for pair := orderedmap.First(response.Links.Value); pair != nil; pair = pair.Next() { + responseLinks.Set(pair.Key().Value, NewLink(pair.Value().Value)) } r.Links = responseLinks } diff --git a/datamodel/high/v3/responses.go b/datamodel/high/v3/responses.go index e1bd722..5a0fafb 100644 --- a/datamodel/high/v3/responses.go +++ b/datamodel/high/v3/responses.go @@ -7,10 +7,10 @@ import ( "fmt" "sort" - "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/high" lowbase "github.com/pb33f/libopenapi/datamodel/low" low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -30,9 +30,9 @@ import ( // be the response for a successful operation call. // - https://spec.openapis.org/oas/v3.1.0#responses-object type Responses struct { - Codes map[string]*Response `json:"-" yaml:"-"` - Default *Response `json:"default,omitempty" yaml:"default,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + Codes orderedmap.Map[string, *Response] `json:"-" yaml:"-"` + Default *Response `json:"default,omitempty" yaml:"default,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Responses } @@ -45,28 +45,31 @@ func NewResponses(responses *low.Responses) *Responses { if !responses.Default.IsEmpty() { r.Default = NewResponse(responses.Default.Value) } - codes := make(map[string]*Response) + codes := orderedmap.New[string, *Response]() type respRes struct { code string resp *Response } - translateFunc := func(key lowbase.KeyReference[string], value lowbase.ValueReference[*low.Response]) (respRes, error) { - return respRes{code: key.Value, resp: NewResponse(value.Value)}, nil + translateFunc := func(pair orderedmap.Pair[lowbase.KeyReference[string], lowbase.ValueReference[*low.Response]]) (asyncResult[*Response], error) { + return asyncResult[*Response]{ + key: pair.Key().Value, + result: NewResponse(pair.Value().Value), + }, nil } - resultFunc := func(value respRes) error { - codes[value.code] = value.resp + resultFunc := func(value asyncResult[*Response]) error { + codes.Set(value.key, value.result) return nil } - _ = datamodel.TranslateMapParallel[lowbase.KeyReference[string], lowbase.ValueReference[*low.Response], respRes](responses.Codes, translateFunc, resultFunc) + _ = orderedmap.TranslateMapParallel[lowbase.KeyReference[string], lowbase.ValueReference[*low.Response], asyncResult[*Response]](responses.Codes, translateFunc, resultFunc) r.Codes = codes return r } // FindResponseByCode is a shortcut for looking up code by an integer vs. a string func (r *Responses) FindResponseByCode(code int) *Response { - return r.Codes[fmt.Sprintf("%d", code)] + return r.Codes.GetOrZero(fmt.Sprintf("%d", code)) } // GoLow returns the low-level Response object used to create the high-level one. @@ -101,16 +104,16 @@ func (r *Responses) MarshalYAML() (interface{}, error) { } var mapped []*responseItem - for k, re := range r.Codes { + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { ln := 9999 // default to a high value to weight new content to the bottom. if r.low != nil { - for lKey := range r.low.Codes { - if lKey.Value == k { - ln = lKey.KeyNode.Line + for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() { + if lPair.Key().Value == pair.Key() { + ln = lPair.Key().KeyNode.Line } } } - mapped = append(mapped, &responseItem{re, k, ln, nil}) + mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil}) } // extract extensions @@ -157,16 +160,16 @@ func (r *Responses) MarshalYAMLInline() (interface{}, error) { } var mapped []*responseItem - for k, re := range r.Codes { + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { ln := 9999 // default to a high value to weight new content to the bottom. if r.low != nil { - for lKey := range r.low.Codes { - if lKey.Value == k { - ln = lKey.KeyNode.Line + for lPair := orderedmap.First(r.low.Codes); lPair != nil; lPair = lPair.Next() { + if lPair.Key().Value == pair.Key() { + ln = lPair.Key().KeyNode.Line } } } - mapped = append(mapped, &responseItem{re, k, ln, nil}) + mapped = append(mapped, &responseItem{pair.Value(), pair.Key(), ln, nil}) } // extract extensions diff --git a/datamodel/high/v3/server.go b/datamodel/high/v3/server.go index 0f9d63e..36c1db5 100644 --- a/datamodel/high/v3/server.go +++ b/datamodel/high/v3/server.go @@ -6,16 +6,17 @@ 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" ) // Server represents a high-level OpenAPI 3+ Server object, that is backed by a low level one. // - https://spec.openapis.org/oas/v3.1.0#server-object type Server struct { - URL string `json:"url,omitempty" yaml:"url,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"` - Extensions map[string]any `json:"-" yaml:"-"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Variables orderedmap.Map[string, *ServerVariable] `json:"variables,omitempty" yaml:"variables,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` low *low.Server } @@ -25,9 +26,9 @@ func NewServer(server *low.Server) *Server { s.low = server s.Description = server.Description.Value s.URL = server.URL.Value - vars := make(map[string]*ServerVariable) - for k, val := range server.Variables.Value { - vars[k.Value] = NewServerVariable(val.Value) + vars := orderedmap.New[string, *ServerVariable]() + for pair := orderedmap.First(server.Variables.Value); pair != nil; pair = pair.Next() { + vars.Set(pair.Key().Value, NewServerVariable(pair.Value().Value)) } s.Variables = vars s.Extensions = high.ExtractExtensions(server.Extensions) diff --git a/datamodel/high/v3/server_test.go b/datamodel/high/v3/server_test.go index bda480c..7177d79 100644 --- a/datamodel/high/v3/server_test.go +++ b/datamodel/high/v3/server_test.go @@ -4,9 +4,11 @@ package v3 import ( - "github.com/stretchr/testify/assert" "strings" "testing" + + "github.com/pb33f/libopenapi/orderedmap" + "github.com/stretchr/testify/assert" ) func TestServer_MarshalYAML(t *testing.T) { @@ -23,11 +25,11 @@ description: the b33f` assert.Equal(t, desired, strings.TrimSpace(string(rend))) // mutate - server.Variables = map[string]*ServerVariable{ + server.Variables = orderedmap.ToOrderedMap(map[string]*ServerVariable{ "rainbow": { Enum: []string{"one", "two", "three"}, }, - } + }) desired = `url: https://pb33f.io description: the b33f diff --git a/datamodel/low/base/schema.go b/datamodel/low/base/schema.go index a0c44b5..f2ee94d 100644 --- a/datamodel/low/base/schema.go +++ b/datamodel/low/base/schema.go @@ -10,6 +10,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" ) @@ -96,8 +97,8 @@ type Schema struct { 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]] + DependentSchemas low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]] + PatternProperties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]] PropertyNames low.NodeReference[*SchemaProxy] UnevaluatedItems low.NodeReference[*SchemaProxy] UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]] @@ -120,7 +121,7 @@ type Schema struct { Required low.NodeReference[[]low.ValueReference[string]] Enum low.NodeReference[[]low.ValueReference[any]] Not low.NodeReference[*SchemaProxy] - Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]] + Properties low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]] AdditionalProperties low.NodeReference[any] Description low.NodeReference[string] ContentEncoding low.NodeReference[string] @@ -305,10 +306,10 @@ func (s *Schema) Hash() [32]byte { for i := range s.Enum.Value { d = append(d, fmt.Sprint(s.Enum.Value[i].Value)) } - propKeys := make([]string, len(s.Properties.Value)) + propKeys := make([]string, orderedmap.Len(s.Properties.Value)) z := 0 - for i := range s.Properties.Value { - propKeys[z] = i.Value + for pair := orderedmap.First(s.Properties.Value); pair != nil; pair = pair.Next() { + propKeys[z] = pair.Key().Value z++ } sort.Strings(propKeys) @@ -414,10 +415,10 @@ func (s *Schema) Hash() [32]byte { d = append(d, fmt.Sprint(s.Anchor.Value)) } - depSchemasKeys := make([]string, len(s.DependentSchemas.Value)) + depSchemasKeys := make([]string, orderedmap.Len(s.DependentSchemas.Value)) z = 0 - for i := range s.DependentSchemas.Value { - depSchemasKeys[z] = i.Value + for pair := orderedmap.First(s.DependentSchemas.Value); pair != nil; pair = pair.Next() { + depSchemasKeys[z] = pair.Key().Value z++ } sort.Strings(depSchemasKeys) @@ -425,10 +426,10 @@ func (s *Schema) Hash() [32]byte { d = append(d, low.GenerateHashString(s.FindDependentSchema(depSchemasKeys[k]).Value)) } - patternPropsKeys := make([]string, len(s.PatternProperties.Value)) + patternPropsKeys := make([]string, orderedmap.Len(s.PatternProperties.Value)) z = 0 - for i := range s.PatternProperties.Value { - patternPropsKeys[z] = i.Value + for pair := orderedmap.First(s.PatternProperties.Value); pair != nil; pair = pair.Next() { + patternPropsKeys[z] = pair.Key().Value z++ } sort.Strings(patternPropsKeys) @@ -490,19 +491,19 @@ func (s *Schema) Hash() [32]byte { // FindProperty will return a ValueReference pointer containing a SchemaProxy pointer // from a property key name. if found func (s *Schema) FindProperty(name string) *low.ValueReference[*SchemaProxy] { - return low.FindItemInMap[*SchemaProxy](name, s.Properties.Value) + return low.FindItemInOrderedMap[*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) + return low.FindItemInOrderedMap[*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) + return low.FindItemInOrderedMap[*SchemaProxy](name, s.PatternProperties.Value) } // GetExtensions returns all extensions for Schema @@ -1067,7 +1068,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { return nil } -func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]], error) { +func buildPropertyMap(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) @@ -1088,7 +1089,7 @@ func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low _, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content) if propsNode != nil { - propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]) + propertyMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*SchemaProxy]]() var currentProp *yaml.Node totalProps := 0 for i, prop := range propsNode.Content { @@ -1119,10 +1120,10 @@ func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low select { case res := <-bChan: completedProps++ - propertyMap[res.k] = res.v + propertyMap.Set(res.k, res.v) } } - return &low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]{ + return &low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]]{ Value: propertyMap, KeyNode: propLabel, ValueNode: propsNode, diff --git a/datamodel/low/base/security_requirement.go b/datamodel/low/base/security_requirement.go index 559b091..cb28551 100644 --- a/datamodel/low/base/security_requirement.go +++ b/datamodel/low/base/security_requirement.go @@ -6,12 +6,14 @@ package base import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // SecurityRequirement is a low-level representation of a Swagger / OpenAPI 3 SecurityRequirement object. @@ -23,7 +25,7 @@ import ( // - https://swagger.io/specification/v2/#securityDefinitionsObject // - https://swagger.io/specification/#security-requirement-object type SecurityRequirement struct { - Requirements low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]] + Requirements low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]] *low.Reference } @@ -33,7 +35,7 @@ func (s *SecurityRequirement) Build(_, root *yaml.Node, _ *index.SpecIndex) erro utils.CheckForMergeNodes(root) s.Reference = new(low.Reference) var labelNode *yaml.Node - valueMap := make(map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]) + valueMap := orderedmap.New[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]() var arr []low.ValueReference[string] for i := range root.Content { if i%2 == 0 { @@ -47,15 +49,18 @@ func (s *SecurityRequirement) Build(_, root *yaml.Node, _ *index.SpecIndex) erro ValueNode: root.Content[i].Content[j], }) } - valueMap[low.KeyReference[string]{ - Value: labelNode.Value, - KeyNode: labelNode, - }] = low.ValueReference[[]low.ValueReference[string]]{ - Value: arr, - ValueNode: root.Content[i], - } + valueMap.Set( + low.KeyReference[string]{ + Value: labelNode.Value, + KeyNode: labelNode, + }, + low.ValueReference[[]low.ValueReference[string]]{ + Value: arr, + ValueNode: root.Content[i], + }, + ) } - s.Requirements = low.ValueReference[map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]]{ + s.Requirements = low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]]{ Value: valueMap, ValueNode: root, } @@ -64,9 +69,9 @@ func (s *SecurityRequirement) Build(_, root *yaml.Node, _ *index.SpecIndex) erro // FindRequirement will attempt to locate a security requirement string from a supplied name. func (s *SecurityRequirement) FindRequirement(name string) []low.ValueReference[string] { - for k := range s.Requirements.Value { - if k.Value == name { - return s.Requirements.Value[k].Value + for pair := orderedmap.First(s.Requirements.Value); pair != nil; pair = pair.Next() { + if pair.Key().Value == name { + return pair.Value().Value } } return nil @@ -74,10 +79,10 @@ func (s *SecurityRequirement) FindRequirement(name string) []low.ValueReference[ // GetKeys returns a string slice of all the keys used in the requirement. func (s *SecurityRequirement) GetKeys() []string { - keys := make([]string, len(s.Requirements.Value)) + keys := make([]string, orderedmap.Len(s.Requirements.Value)) z := 0 - for k := range s.Requirements.Value { - keys[z] = k.Value + for pair := orderedmap.First(s.Requirements.Value); pair != nil; pair = pair.Next() { + keys[z] = pair.Key().Value } return keys } @@ -85,18 +90,17 @@ func (s *SecurityRequirement) GetKeys() []string { // Hash will return a consistent SHA256 Hash of the SecurityRequirement object func (s *SecurityRequirement) Hash() [32]byte { var f []string - values := make(map[string][]string, len(s.Requirements.Value)) + values := make(map[string][]string, orderedmap.Len(s.Requirements.Value)) var valKeys []string - for k := range s.Requirements.Value { + for pair := orderedmap.First(s.Requirements.Value); pair != nil; pair = pair.Next() { var vals []string - for y := range s.Requirements.Value[k].Value { - vals = append(vals, s.Requirements.Value[k].Value[y].Value) - // lol, I know. -------^^^^^ <- this is the actual value. + for y := range pair.Value().Value { + vals = append(vals, pair.Value().Value[y].Value) } sort.Strings(vals) - valKeys = append(valKeys, k.Value) + valKeys = append(valKeys, pair.Key().Value) if len(vals) > 0 { - values[k.Value] = vals + values[pair.Key().Value] = vals } } sort.Strings(valKeys) diff --git a/datamodel/low/extraction_functions.go b/datamodel/low/extraction_functions.go index e6654d6..f5143f5 100644 --- a/datamodel/low/extraction_functions.go +++ b/datamodel/low/extraction_functions.go @@ -6,13 +6,15 @@ package low import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" - "gopkg.in/yaml.v3" "reflect" "strconv" "strings" + + "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" + "github.com/pb33f/libopenapi/utils" + "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" + "gopkg.in/yaml.v3" ) // FindItemInMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. Every @@ -29,6 +31,22 @@ func FindItemInMap[T any](item string, collection map[KeyReference[string]]Value return nil } +// FindItemInOrderedMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. +// Every KeyReference will have its value checked against the string key and if there is a match, it will be +// returned. +func FindItemInOrderedMap[T any](item string, collection orderedmap.Map[KeyReference[string], ValueReference[T]]) *ValueReference[T] { + for pair := orderedmap.First(collection); pair != nil; pair = pair.Next() { + n := pair.Key() + if n.Value == item { + return pair.ValuePtr() + } + if strings.EqualFold(item, n.Value) { + return pair.ValuePtr() + } + } + return nil +} + // helper function to generate a list of all the things an index should be searched for. func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Reference { return []func() map[string]*index.Reference{ @@ -366,8 +384,8 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any]( root *yaml.Node, idx *index.SpecIndex, includeExtensions bool, -) (map[KeyReference[string]]ValueReference[PT], error) { - valueMap := make(map[KeyReference[string]]ValueReference[PT]) +) (orderedmap.Map[KeyReference[string], ValueReference[PT]], error) { + valueMap := orderedmap.New[KeyReference[string], ValueReference[PT]]() var circError error if utils.IsNodeMap(root) { var currentKey *yaml.Node @@ -431,15 +449,18 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any]( SetReference(n, referenceValue) } if currentKey != nil { - valueMap[KeyReference[string]{ - Value: currentKey.Value, - KeyNode: currentKey, - }] = ValueReference[PT]{ - Value: n, - ValueNode: node, - //IsReference: isReference, - Reference: referenceValue, - } + valueMap.Set( + KeyReference[string]{ + Value: currentKey.Value, + KeyNode: currentKey, + }, + ValueReference[PT]{ + Value: n, + ValueNode: node, + //IsReference: isReference, + Reference: referenceValue, + }, + ) } } } @@ -458,7 +479,7 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any]( func ExtractMapNoLookup[PT Buildable[N], N any]( root *yaml.Node, idx *index.SpecIndex, -) (map[KeyReference[string]]ValueReference[PT], error) { +) (orderedmap.Map[KeyReference[string], ValueReference[PT]], error) { return ExtractMapNoLookupExtensions[PT, N](root, idx, false) } @@ -478,7 +499,7 @@ func ExtractMapExtensions[PT Buildable[N], N any]( root *yaml.Node, idx *index.SpecIndex, extensions bool, -) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) { +) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) { //var isReference bool var referenceValue string var labelNode, valueNode *yaml.Node @@ -522,8 +543,9 @@ func ExtractMapExtensions[PT Buildable[N], N any]( } if valueNode != nil { var currentLabelNode *yaml.Node - valueMap := make(map[KeyReference[string]]ValueReference[PT]) + valueMap := orderedmap.New[KeyReference[string], ValueReference[PT]]() + // TODO: Convert to datamodel.TranslatePipeline. bChan := make(chan mappingResult[PT]) eChan := make(chan error) @@ -598,7 +620,7 @@ func ExtractMapExtensions[PT Buildable[N], N any]( return valueMap, labelNode, valueNode, err case res := <-bChan: completedKeys++ - valueMap[res.k] = res.v + valueMap.Set(res.k, res.v) } } if circError != nil && !idx.AllowCircularReferenceResolving() { @@ -618,7 +640,7 @@ func ExtractMap[PT Buildable[N], N any]( label string, root *yaml.Node, idx *index.SpecIndex, -) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) { +) (orderedmap.Map[KeyReference[string], ValueReference[PT]], *yaml.Node, *yaml.Node, error) { return ExtractMapExtensions[PT, N](label, root, idx, false) } diff --git a/datamodel/low/extraction_functions_test.go b/datamodel/low/extraction_functions_test.go index 926773d..d97dd87 100644 --- a/datamodel/low/extraction_functions_test.go +++ b/datamodel/low/extraction_functions_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/resolver" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" @@ -795,7 +796,7 @@ func TestExtractArray_Ref_Nested(t *testing.T) { errs := resolve.CheckForCircularReferences() assert.Len(t, errs, 1) - yml = `limes: + yml = `limes: $ref: '#/components/schemas/let-us-eat-cake'` var cNode yaml.Node @@ -824,7 +825,7 @@ func TestExtractArray_Ref_Nested_Circular(t *testing.T) { errs := resolve.CheckForCircularReferences() assert.Len(t, errs, 1) - yml = `limes: + yml = `limes: - $ref: '#/components/schemas/things'` var cNode yaml.Node @@ -851,7 +852,7 @@ func TestExtractArray_Ref_Nested_BadRef(t *testing.T) { assert.NoError(t, mErr) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateClosedAPIIndexConfig()) - yml = `limes: + yml = `limes: - $ref: '#/components/schemas/thangs'` var cNode yaml.Node @@ -880,7 +881,7 @@ func TestExtractArray_Ref_Nested_CircularFlat(t *testing.T) { errs := resolve.CheckForCircularReferences() assert.Len(t, errs, 1) - yml = `limes: + yml = `limes: $ref: '#/components/schemas/things'` var cNode yaml.Node @@ -902,7 +903,7 @@ func TestExtractArray_BadBuild(t *testing.T) { assert.NoError(t, mErr) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateClosedAPIIndexConfig()) - yml = `limes: + yml = `limes: - dontWork: 1` var cNode yaml.Node @@ -994,12 +995,12 @@ one: assert.NoError(t, err) assert.Len(t, things, 2) - for k, v := range things { - if k.Value == "x-hey" { + for pair := orderedmap.First(things); pair != nil; pair = pair.Next() { + if pair.Key().Value == "x-hey" { continue } - assert.Equal(t, "one", k.Value) - assert.Len(t, v.ValueNode.Content, 2) + assert.Equal(t, "one", pair.Key().Value) + assert.Len(t, pair.Value().ValueNode.Content, 2) } } @@ -1050,8 +1051,8 @@ one: assert.NoError(t, err) assert.Len(t, things, 1) - for k := range things { - assert.Equal(t, "one", k.Value) + for pair := orderedmap.First(things); pair != nil; pair = pair.Next() { + assert.Equal(t, "one", pair.Key().Value) } } @@ -1283,8 +1284,8 @@ one: assert.NoError(t, err) assert.Len(t, things, 1) - for k := range things { - assert.Equal(t, 99, things[k].Value.AlmostWork.Value) + for pair := orderedmap.First(things); pair != nil; pair = pair.Next() { + assert.Equal(t, 99, pair.Value().Value.AlmostWork.Value) } } @@ -1313,8 +1314,8 @@ func TestExtractMapFlat_DoubleRef(t *testing.T) { assert.NoError(t, err) assert.Len(t, things, 1) - for k := range things { - assert.Equal(t, 99, things[k].Value.AlmostWork.Value) + for pair := orderedmap.First(things); pair != nil; pair = pair.Next() { + assert.Equal(t, 99, pair.Value().Value.AlmostWork.Value) } } diff --git a/datamodel/low/v2/definitions.go b/datamodel/low/v2/definitions.go index d659936..ab5065a 100644 --- a/datamodel/low/v2/definitions.go +++ b/datamodel/low/v2/definitions.go @@ -5,13 +5,15 @@ package v2 import ( "crypto/sha256" + "sort" + "strings" + "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/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" - "strings" ) // ParameterDefinitions is a low-level representation of a Swagger / OpenAPI 2 Parameters Definitions object. @@ -20,7 +22,7 @@ import ( // referenced to the ones defined here. It does not define global operation parameters // - https://swagger.io/specification/v2/#parametersDefinitionsObject type ParameterDefinitions struct { - Definitions map[low.KeyReference[string]]low.ValueReference[*Parameter] + Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*Parameter]] } // ResponsesDefinitions is a low-level representation of a Swagger / OpenAPI 2 Responses Definitions object. @@ -29,7 +31,7 @@ type ParameterDefinitions struct { // referenced to the ones defined here. It does not define global operation responses // - https://swagger.io/specification/v2/#responsesDefinitionsObject type ResponsesDefinitions struct { - Definitions map[low.KeyReference[string]]low.ValueReference[*Response] + Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]] } // SecurityDefinitions is a low-level representation of a Swagger / OpenAPI 2 Security Definitions object. @@ -38,7 +40,7 @@ type ResponsesDefinitions struct { // schemes on the operations and only serves to provide the relevant details for each scheme // - https://swagger.io/specification/v2/#securityDefinitionsObject type SecurityDefinitions struct { - Definitions map[low.KeyReference[string]]low.ValueReference[*SecurityScheme] + Definitions orderedmap.Map[low.KeyReference[string], low.ValueReference[*SecurityScheme]] } // Definitions is a low-level representation of a Swagger / OpenAPI 2 Definitions object @@ -47,33 +49,34 @@ type SecurityDefinitions struct { // arrays or models. // - https://swagger.io/specification/v2/#definitionsObject type Definitions struct { - Schemas map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy] + Schemas orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]] } // FindSchema will attempt to locate a base.SchemaProxy instance using a name. func (d *Definitions) FindSchema(schema string) *low.ValueReference[*base.SchemaProxy] { - return low.FindItemInMap[*base.SchemaProxy](schema, d.Schemas) + return low.FindItemInOrderedMap[*base.SchemaProxy](schema, d.Schemas) } // FindParameter will attempt to locate a Parameter instance using a name. func (pd *ParameterDefinitions) FindParameter(parameter string) *low.ValueReference[*Parameter] { - return low.FindItemInMap[*Parameter](parameter, pd.Definitions) + return low.FindItemInOrderedMap[*Parameter](parameter, pd.Definitions) } // FindResponse will attempt to locate a Response instance using a name. func (r *ResponsesDefinitions) FindResponse(response string) *low.ValueReference[*Response] { - return low.FindItemInMap[*Response](response, r.Definitions) + return low.FindItemInOrderedMap[*Response](response, r.Definitions) } // FindSecurityDefinition will attempt to locate a SecurityScheme using a name. func (s *SecurityDefinitions) FindSecurityDefinition(securityDef string) *low.ValueReference[*SecurityScheme] { - return low.FindItemInMap[*SecurityScheme](securityDef, s.Definitions) + return low.FindItemInOrderedMap[*SecurityScheme](securityDef, s.Definitions) } // Build will extract all definitions into SchemaProxy instances. func (d *Definitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error { root = utils.NodeAlias(root) utils.CheckForMergeNodes(root) + // TODO: Refactor with orderedmap.TranslatePipeline. errorChan := make(chan error) resultChan := make(chan definitionResult[*base.SchemaProxy]) var defLabel *yaml.Node @@ -99,17 +102,18 @@ func (d *Definitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error { } completedDefs := 0 - results := make(map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy]) + results := orderedmap.New[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]() for completedDefs < totalDefinitions { select { case err := <-errorChan: return err case sch := <-resultChan: completedDefs++ - results[low.KeyReference[string]{ + key := low.KeyReference[string]{ Value: sch.k.Value, KeyNode: sch.k, - }] = sch.v + } + results.Set(key, sch.v) } } d.Schemas = results @@ -119,10 +123,10 @@ func (d *Definitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error { // Hash will return a consistent SHA256 Hash of the Definitions object func (d *Definitions) Hash() [32]byte { var f []string - keys := make([]string, len(d.Schemas)) + keys := make([]string, orderedmap.Len(d.Schemas)) z := 0 - for k := range d.Schemas { - keys[z] = k.Value + for pair := orderedmap.First(d.Schemas); pair != nil; pair = pair.Next() { + keys[z] = pair.Key().Value z++ } sort.Strings(keys) @@ -158,17 +162,18 @@ func (pd *ParameterDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) } completedDefs := 0 - results := make(map[low.KeyReference[string]]low.ValueReference[*Parameter]) + results := orderedmap.New[low.KeyReference[string], low.ValueReference[*Parameter]]() for completedDefs < totalDefinitions { select { case err := <-errorChan: return err case sch := <-resultChan: completedDefs++ - results[low.KeyReference[string]{ + key := low.KeyReference[string]{ Value: sch.k.Value, KeyNode: sch.k, - }] = sch.v + } + results.Set(key, sch.v) } } pd.Definitions = results @@ -207,17 +212,18 @@ func (r *ResponsesDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) e } completedDefs := 0 - results := make(map[low.KeyReference[string]]low.ValueReference[*Response]) + results := orderedmap.New[low.KeyReference[string], low.ValueReference[*Response]]() for completedDefs < totalDefinitions { select { case err := <-errorChan: return err case sch := <-resultChan: completedDefs++ - results[low.KeyReference[string]{ + key := low.KeyReference[string]{ Value: sch.k.Value, KeyNode: sch.k, - }] = sch.v + } + results.Set(key, sch.v) } } r.Definitions = results @@ -253,17 +259,18 @@ func (s *SecurityDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) er } completedDefs := 0 - results := make(map[low.KeyReference[string]]low.ValueReference[*SecurityScheme]) + results := orderedmap.New[low.KeyReference[string], low.ValueReference[*SecurityScheme]]() for completedDefs < totalDefinitions { select { case err := <-errorChan: return err case sch := <-resultChan: completedDefs++ - results[low.KeyReference[string]{ + key := low.KeyReference[string]{ Value: sch.k.Value, KeyNode: sch.k, - }] = sch.v + } + results.Set(key, sch.v) } } s.Definitions = results diff --git a/datamodel/low/v2/examples.go b/datamodel/low/v2/examples.go index 8bf77cc..dfb37c2 100644 --- a/datamodel/low/v2/examples.go +++ b/datamodel/low/v2/examples.go @@ -6,24 +6,26 @@ package v2 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // Examples represents a low-level Swagger / OpenAPI 2 Example object. // Allows sharing examples for operation responses // - https://swagger.io/specification/v2/#exampleObject type Examples struct { - Values map[low.KeyReference[string]]low.ValueReference[any] + Values orderedmap.Map[low.KeyReference[string], low.ValueReference[any]] } // FindExample attempts to locate an example value, using a key label. func (e *Examples) FindExample(name string) *low.ValueReference[any] { - return low.FindItemInMap[any](name, e.Values) + return low.FindItemInOrderedMap[any](name, e.Values) } // Build will extract all examples and will attempt to unmarshal content into a map or slice based on type. @@ -32,7 +34,7 @@ func (e *Examples) Build(_, root *yaml.Node, _ *index.SpecIndex) error { utils.CheckForMergeNodes(root) var keyNode, currNode *yaml.Node var err error - e.Values = make(map[low.KeyReference[string]]low.ValueReference[any]) + e.Values = orderedmap.New[low.KeyReference[string], low.ValueReference[any]]() for i := range root.Content { if i%2 == 0 { keyNode = root.Content[i] @@ -48,32 +50,40 @@ func (e *Examples) Build(_, root *yaml.Node, _ *index.SpecIndex) error { // lets just default to interface var j interface{} _ = currNode.Decode(&j) - e.Values[low.KeyReference[string]{ - Value: keyNode.Value, - KeyNode: keyNode, - }] = low.ValueReference[any]{ - Value: j, - ValueNode: currNode, - } + e.Values.Set( + low.KeyReference[string]{ + Value: keyNode.Value, + KeyNode: keyNode, + }, + low.ValueReference[any]{ + Value: j, + ValueNode: currNode, + }, + ) continue } - e.Values[low.KeyReference[string]{ - Value: keyNode.Value, - KeyNode: keyNode, - }] = low.ValueReference[any]{ - Value: k, - ValueNode: currNode, - } + e.Values.Set( + low.KeyReference[string]{ + Value: keyNode.Value, + KeyNode: keyNode, + }, + low.ValueReference[any]{ + Value: k, + ValueNode: currNode, + }, + ) continue } - e.Values[low.KeyReference[string]{ - Value: keyNode.Value, - KeyNode: keyNode, - }] = low.ValueReference[any]{ - Value: n, - ValueNode: currNode, - } - + e.Values.Set( + low.KeyReference[string]{ + Value: keyNode.Value, + KeyNode: keyNode, + }, + low.ValueReference[any]{ + Value: n, + ValueNode: currNode, + }, + ) } return nil } @@ -81,10 +91,10 @@ func (e *Examples) Build(_, root *yaml.Node, _ *index.SpecIndex) error { // Hash will return a consistent SHA256 Hash of the Examples object func (e *Examples) Hash() [32]byte { var f []string - keys := make([]string, len(e.Values)) + keys := make([]string, orderedmap.Len(e.Values)) z := 0 - for k := range e.Values { - keys[z] = k.Value + for pair := orderedmap.First(e.Values); pair != nil; pair = pair.Next() { + keys[z] = pair.Key().Value z++ } sort.Strings(keys) diff --git a/datamodel/low/v2/paths.go b/datamodel/low/v2/paths.go index 1165b3c..9308ca8 100644 --- a/datamodel/low/v2/paths.go +++ b/datamodel/low/v2/paths.go @@ -6,7 +6,6 @@ package v2 import ( "crypto/sha256" "fmt" - "io" "sort" "strings" "sync" @@ -32,28 +31,24 @@ func (p *Paths) GetExtensions() map[low.KeyReference[string]]low.ValueReference[ // FindPath attempts to locate a PathItem instance, given a path key. func (p *Paths) FindPath(path string) (result *low.ValueReference[*PathItem]) { - action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { if pair.Key().Value == path { result = pair.ValuePtr() - return io.EOF + break } - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action) return result } // FindPathAndKey attempts to locate a PathItem instance, given a path key. func (p *Paths) FindPathAndKey(path string) (key *low.KeyReference[string], value *low.ValueReference[*PathItem]) { - action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { if pair.Key().Value == path { key = pair.KeyPtr() value = pair.ValuePtr() - return io.EOF + break } - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action) return key, value } @@ -167,14 +162,12 @@ func (p *Paths) Hash() [32]byte { keys := make(map[string]low.ValueReference[*PathItem]) z := 0 - action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { k := pair.Key().Value keys[k] = pair.Value() l[z] = k z++ - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action) sort.Strings(l) for k := range l { diff --git a/datamodel/low/v2/response.go b/datamodel/low/v2/response.go index 266adc1..6198f36 100644 --- a/datamodel/low/v2/response.go +++ b/datamodel/low/v2/response.go @@ -6,13 +6,15 @@ package v2 import ( "crypto/sha256" "fmt" + "sort" + "strings" + "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/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" - "strings" ) // Response is a representation of a high-level Swagger / OpenAPI 2 Response object, backed by a low-level one. @@ -22,7 +24,7 @@ import ( type Response struct { Description low.NodeReference[string] Schema low.NodeReference[*base.SchemaProxy] - Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]] + Headers low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Header]]] Examples low.NodeReference[*Examples] Extensions map[low.KeyReference[string]]low.ValueReference[any] } @@ -39,7 +41,7 @@ func (r *Response) GetExtensions() map[low.KeyReference[string]]low.ValueReferen // FindHeader will attempt to locate a Header value, given a key func (r *Response) FindHeader(hType string) *low.ValueReference[*Header] { - return low.FindItemInMap[*Header](hType, r.Headers.Value) + return low.FindItemInOrderedMap[*Header](hType, r.Headers.Value) } // Build will extract schema, extensions, examples and headers from node @@ -68,7 +70,7 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return err } if headers != nil { - r.Headers = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]]{ + r.Headers = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Header]]]{ Value: headers, KeyNode: lN, ValueNode: kN, @@ -87,8 +89,8 @@ func (r *Response) Hash() [32]byte { f = append(f, low.GenerateHashString(r.Schema.Value)) } if !r.Examples.IsEmpty() { - for k := range r.Examples.Value.Values { - f = append(f, low.GenerateHashString(r.Examples.Value.Values[k].Value)) + for pair := orderedmap.First(r.Examples.Value.Values); pair != nil; pair = pair.Next() { + f = append(f, low.GenerateHashString(pair.Value().Value)) } } keys := make([]string, len(r.Extensions)) diff --git a/datamodel/low/v2/responses.go b/datamodel/low/v2/responses.go index 43a6942..a7699f8 100644 --- a/datamodel/low/v2/responses.go +++ b/datamodel/low/v2/responses.go @@ -6,17 +6,19 @@ package v2 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // Responses is a low-level representation of a Swagger / OpenAPI 2 Responses object. type Responses struct { - Codes map[low.KeyReference[string]]low.ValueReference[*Response] + Codes orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]] Default low.NodeReference[*Response] Extensions map[low.KeyReference[string]]low.ValueReference[any] } @@ -55,12 +57,12 @@ func (r *Responses) Build(_, root *yaml.Node, idx *index.SpecIndex) error { } func (r *Responses) getDefault() *low.NodeReference[*Response] { - for n, o := range r.Codes { - if strings.ToLower(n.Value) == DefaultLabel { + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { + if strings.ToLower(pair.Key().Value) == DefaultLabel { return &low.NodeReference[*Response]{ - ValueNode: o.ValueNode, - KeyNode: n.KeyNode, - Value: o.Value, + ValueNode: pair.Value().ValueNode, + KeyNode: pair.Key().KeyNode, + Value: pair.Value().Value, } } } @@ -71,34 +73,34 @@ func (r *Responses) getDefault() *low.NodeReference[*Response] { func (r *Responses) deleteCode(code string) { var key *low.KeyReference[string] if r.Codes != nil { - for k := range r.Codes { - if k.Value == code { - key = &k + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { + if pair.Key().Value == code { + key = pair.KeyPtr() break } } } // should never be nil, but, you never know... science and all that! if key != nil { - delete(r.Codes, *key) + r.Codes.Delete(*key) } } // FindResponseByCode will attempt to locate a Response instance using an HTTP response code string. func (r *Responses) FindResponseByCode(code string) *low.ValueReference[*Response] { - return low.FindItemInMap[*Response](code, r.Codes) + return low.FindItemInOrderedMap[*Response](code, r.Codes) } // Hash will return a consistent SHA256 Hash of the Examples object func (r *Responses) Hash() [32]byte { var f []string var keys []string - keys = make([]string, len(r.Codes)) + keys = make([]string, orderedmap.Len(r.Codes)) cmap := make(map[string]*Response, len(keys)) z := 0 - for k := range r.Codes { - keys[z] = k.Value - cmap[k.Value] = r.Codes[k].Value + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { + keys[z] = pair.Key().Value + cmap[pair.Key().Value] = pair.Value().Value z++ } sort.Strings(keys) diff --git a/datamodel/low/v2/scopes.go b/datamodel/low/v2/scopes.go index 87cc0a2..d2ba4ed 100644 --- a/datamodel/low/v2/scopes.go +++ b/datamodel/low/v2/scopes.go @@ -6,12 +6,14 @@ package v2 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // Scopes is a low-level representation of a Swagger / OpenAPI 2 OAuth2 Scopes object. @@ -19,7 +21,7 @@ import ( // Scopes lists the available scopes for an OAuth2 security scheme. // - https://swagger.io/specification/v2/#scopesObject type Scopes struct { - Values map[low.KeyReference[string]]low.ValueReference[string] + Values orderedmap.Map[low.KeyReference[string], low.ValueReference[string]] Extensions map[low.KeyReference[string]]low.ValueReference[any] } @@ -30,7 +32,7 @@ func (s *Scopes) GetExtensions() map[low.KeyReference[string]]low.ValueReference // FindScope will attempt to locate a scope string using a key. func (s *Scopes) FindScope(scope string) *low.ValueReference[string] { - return low.FindItemInMap[string](scope, s.Values) + return low.FindItemInOrderedMap[string](scope, s.Values) } // Build will extract scope values and extensions from node. @@ -38,20 +40,23 @@ func (s *Scopes) Build(_, root *yaml.Node, idx *index.SpecIndex) error { root = utils.NodeAlias(root) utils.CheckForMergeNodes(root) s.Extensions = low.ExtractExtensions(root) - valueMap := make(map[low.KeyReference[string]]low.ValueReference[string]) + valueMap := orderedmap.New[low.KeyReference[string], low.ValueReference[string]]() if utils.IsNodeMap(root) { for k := range root.Content { if k%2 == 0 { if strings.Contains(root.Content[k].Value, "x-") { continue } - valueMap[low.KeyReference[string]{ - Value: root.Content[k].Value, - KeyNode: root.Content[k], - }] = low.ValueReference[string]{ - Value: root.Content[k+1].Value, - ValueNode: root.Content[k+1], - } + valueMap.Set( + low.KeyReference[string]{ + Value: root.Content[k].Value, + KeyNode: root.Content[k], + }, + low.ValueReference[string]{ + Value: root.Content[k+1].Value, + ValueNode: root.Content[k+1], + }, + ) } } s.Values = valueMap @@ -62,12 +67,12 @@ func (s *Scopes) Build(_, root *yaml.Node, idx *index.SpecIndex) error { // Hash will return a consistent SHA256 Hash of the Scopes object func (s *Scopes) Hash() [32]byte { var f []string - vals := make(map[string]low.ValueReference[string], len(s.Values)) - keys := make([]string, len(s.Values)) + vals := make(map[string]low.ValueReference[string], orderedmap.Len(s.Values)) + keys := make([]string, orderedmap.Len(s.Values)) z := 0 - for k := range s.Values { - keys[z] = k.Value - vals[k.Value] = s.Values[k] + for pair := orderedmap.First(s.Values); pair != nil; pair = pair.Next() { + keys[z] = pair.Key().Value + vals[pair.Key().Value] = pair.Value() z++ } sort.Strings(keys) diff --git a/datamodel/low/v3/callback.go b/datamodel/low/v3/callback.go index bd66c27..0dc12df 100644 --- a/datamodel/low/v3/callback.go +++ b/datamodel/low/v3/callback.go @@ -6,10 +6,12 @@ package v3 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/utils" "sort" "strings" + "github.com/pb33f/libopenapi/orderedmap" + "github.com/pb33f/libopenapi/utils" + "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" @@ -23,7 +25,7 @@ import ( // that identifies a URL to use for the callback operation. // - https://spec.openapis.org/oas/v3.1.0#callback-object type Callback struct { - Expression low.ValueReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]] + Expression low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*PathItem]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference } @@ -35,7 +37,7 @@ func (cb *Callback) GetExtensions() map[low.KeyReference[string]]low.ValueRefere // FindExpression will locate a string expression and return a ValueReference containing the located PathItem func (cb *Callback) FindExpression(exp string) *low.ValueReference[*PathItem] { - return low.FindItemInMap[*PathItem](exp, cb.Expression.Value) + return low.FindItemInOrderedMap[*PathItem](exp, cb.Expression.Value) } // Build will extract extensions, expressions and PathItem objects for Callback @@ -47,7 +49,7 @@ func (cb *Callback) Build(_, root *yaml.Node, idx *index.SpecIndex) error { // handle callback var currentCB *yaml.Node - callbacks := make(map[low.KeyReference[string]]low.ValueReference[*PathItem]) + callbacks := orderedmap.New[low.KeyReference[string], low.ValueReference[*PathItem]]() for i, callbackNode := range root.Content { if i%2 == 0 { @@ -61,17 +63,20 @@ func (cb *Callback) Build(_, root *yaml.Node, idx *index.SpecIndex) error { if eErr != nil { return eErr } - callbacks[low.KeyReference[string]{ - Value: currentCB.Value, - KeyNode: currentCB, - }] = low.ValueReference[*PathItem]{ - Value: callback, - ValueNode: callbackNode, - Reference: rv, - } + callbacks.Set( + low.KeyReference[string]{ + Value: currentCB.Value, + KeyNode: currentCB, + }, + low.ValueReference[*PathItem]{ + Value: callback, + ValueNode: callbackNode, + Reference: rv, + }, + ) } - if len(callbacks) > 0 { - cb.Expression = low.ValueReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]]{ + if orderedmap.Len(callbacks) > 0 { + cb.Expression = low.ValueReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*PathItem]]]{ Value: callbacks, ValueNode: root, } @@ -83,10 +88,10 @@ func (cb *Callback) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (cb *Callback) Hash() [32]byte { var f []string var keys []string - keys = make([]string, len(cb.Expression.Value)) + keys = make([]string, orderedmap.Len(cb.Expression.Value)) z := 0 - for k := range cb.Expression.Value { - keys[z] = low.GenerateHashString(cb.Expression.Value[k].Value) + for pair := orderedmap.First(cb.Expression.Value); pair != nil; pair = pair.Next() { + keys[z] = low.GenerateHashString(pair.Value().Value) z++ } sort.Strings(keys) diff --git a/datamodel/low/v3/components.go b/datamodel/low/v3/components.go index d8b6f8d..ed28390 100644 --- a/datamodel/low/v3/components.go +++ b/datamodel/low/v3/components.go @@ -14,6 +14,7 @@ import ( "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/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -24,15 +25,15 @@ import ( // will have no effect on the API unless they are explicitly referenced from properties outside the components object. // - https://spec.openapis.org/oas/v3.1.0#components-object type Components struct { - Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy]] - Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]] - Parameters low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Parameter]] - Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]] - RequestBodies low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*RequestBody]] - Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]] - SecuritySchemes low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SecurityScheme]] - Links low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]] - Callbacks low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]] + Schemas low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]]] + Responses low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]]] + Parameters low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Parameter]]] + Examples low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]] + RequestBodies low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*RequestBody]]] + Headers low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Header]]] + SecuritySchemes low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*SecurityScheme]]] + Links low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Link]]] + Callbacks low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Callback]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference } @@ -75,16 +76,16 @@ func (co *Components) Hash() [32]byte { return sha256.Sum256([]byte(strings.Join(f, "|"))) } -func generateHashForObjectMap[T any](collection map[low.KeyReference[string]]low.ValueReference[T], hash *[]string) { +func generateHashForObjectMap[T any](collection orderedmap.Map[low.KeyReference[string], low.ValueReference[T]], hash *[]string) { if collection == nil { return } - l := make([]string, len(collection)) + l := make([]string, orderedmap.Len(collection)) keys := make(map[string]low.ValueReference[T]) z := 0 - for k := range collection { - keys[k.Value] = collection[k] - l[z] = k.Value + for pair := orderedmap.First(collection); pair != nil; pair = pair.Next() { + keys[pair.Key().Value] = pair.Value() + l[z] = pair.Key().Value z++ } sort.Strings(l) @@ -100,43 +101,43 @@ func (co *Components) FindExtension(ext string) *low.ValueReference[any] { // FindSchema attempts to locate a SchemaProxy from 'schemas' with a specific name func (co *Components) FindSchema(schema string) *low.ValueReference[*base.SchemaProxy] { - return low.FindItemInMap[*base.SchemaProxy](schema, co.Schemas.Value) + return low.FindItemInOrderedMap[*base.SchemaProxy](schema, co.Schemas.Value) } // FindResponse attempts to locate a Response from 'responses' with a specific name func (co *Components) FindResponse(response string) *low.ValueReference[*Response] { - return low.FindItemInMap[*Response](response, co.Responses.Value) + return low.FindItemInOrderedMap[*Response](response, co.Responses.Value) } // FindParameter attempts to locate a Parameter from 'parameters' with a specific name func (co *Components) FindParameter(response string) *low.ValueReference[*Parameter] { - return low.FindItemInMap[*Parameter](response, co.Parameters.Value) + return low.FindItemInOrderedMap[*Parameter](response, co.Parameters.Value) } // FindSecurityScheme attempts to locate a SecurityScheme from 'securitySchemes' with a specific name func (co *Components) FindSecurityScheme(sScheme string) *low.ValueReference[*SecurityScheme] { - return low.FindItemInMap[*SecurityScheme](sScheme, co.SecuritySchemes.Value) + return low.FindItemInOrderedMap[*SecurityScheme](sScheme, co.SecuritySchemes.Value) } // FindExample attempts tp func (co *Components) FindExample(example string) *low.ValueReference[*base.Example] { - return low.FindItemInMap[*base.Example](example, co.Examples.Value) + return low.FindItemInOrderedMap[*base.Example](example, co.Examples.Value) } func (co *Components) FindRequestBody(requestBody string) *low.ValueReference[*RequestBody] { - return low.FindItemInMap[*RequestBody](requestBody, co.RequestBodies.Value) + return low.FindItemInOrderedMap[*RequestBody](requestBody, co.RequestBodies.Value) } func (co *Components) FindHeader(header string) *low.ValueReference[*Header] { - return low.FindItemInMap[*Header](header, co.Headers.Value) + return low.FindItemInOrderedMap[*Header](header, co.Headers.Value) } func (co *Components) FindLink(link string) *low.ValueReference[*Link] { - return low.FindItemInMap[*Link](link, co.Links.Value) + return low.FindItemInOrderedMap[*Link](link, co.Links.Value) } func (co *Components) FindCallback(callback string) *low.ValueReference[*Callback] { - return low.FindItemInMap[*Callback](callback, co.Callbacks.Value) + return low.FindItemInOrderedMap[*Callback](callback, co.Callbacks.Value) } // Build converts root YAML node containing components to low level model. @@ -222,13 +223,13 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error { // extractComponentValues converts all the YAML nodes of a component type to // low level model. // Process each node in parallel. -func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]], error) { - var emptyResult low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]] +func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]], error) { + var emptyResult low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]] _, nodeLabel, nodeValue := utils.FindKeyNodeFullTop(label, root.Content) if nodeValue == nil { return emptyResult, nil } - componentValues := make(map[low.KeyReference[string]]low.ValueReference[T]) + componentValues := orderedmap.New[low.KeyReference[string], low.ValueReference[T]]() if utils.IsNodeArray(nodeValue) { return emptyResult, fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column) } @@ -271,7 +272,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml. // Collect output. go func() { for result := range out { - componentValues[result.key] = result.value + componentValues.Set(result.key, result.value) } close(done) wg.Done() @@ -317,7 +318,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml. return emptyResult, err } - results := low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]{ + results := low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]]{ KeyNode: nodeLabel, ValueNode: nodeValue, Value: componentValues, diff --git a/datamodel/low/v3/create_document.go b/datamodel/low/v3/create_document.go index 85cc843..f363481 100644 --- a/datamodel/low/v3/create_document.go +++ b/datamodel/low/v3/create_document.go @@ -9,6 +9,7 @@ import ( "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/pb33f/libopenapi/resolver" "github.com/pb33f/libopenapi/utils" ) @@ -247,7 +248,7 @@ func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInd return eErr } if hooks != nil { - doc.Webhooks = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]]{ + doc.Webhooks = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*PathItem]]]{ Value: hooks, KeyNode: hooksL, ValueNode: hooksN, diff --git a/datamodel/low/v3/create_document_test.go b/datamodel/low/v3/create_document_test.go index 5f72764..9174d16 100644 --- a/datamodel/low/v3/create_document_test.go +++ b/datamodel/low/v3/create_document_test.go @@ -7,6 +7,7 @@ import ( "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/pb33f/libopenapi/orderedmap" "github.com/stretchr/testify/assert" ) @@ -225,10 +226,10 @@ func TestCreateDocument_Info(t *testing.T) { func TestCreateDocument_WebHooks(t *testing.T) { initTest() assert.Len(t, doc.Webhooks.Value, 1) - for i := range doc.Webhooks.Value { + for pair := orderedmap.First(doc.Webhooks.Value); pair != nil; pair = pair.Next() { // a nice deep model should be available for us. assert.Equal(t, "Information about a new burger", - doc.Webhooks.Value[i].Value.Post.Value.RequestBody.Value.Description.Value) + pair.Value().Value.Post.Value.RequestBody.Value.Description.Value) } } diff --git a/datamodel/low/v3/document.go b/datamodel/low/v3/document.go index cec319c..9b1f282 100644 --- a/datamodel/low/v3/document.go +++ b/datamodel/low/v3/document.go @@ -12,6 +12,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/orderedmap" ) type Document struct { @@ -37,7 +38,7 @@ type Document struct { // for example by an out-of-band registration. The key name is a unique string to refer to each webhook, // while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider // and the expected responses. An example is available. - Webhooks low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]] // 3.1 + Webhooks low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*PathItem]]] // 3.1 // Servers is a slice of Server instances which provide connectivity information to a target server. If the servers // property is not provided, or is an empty array, the default value would be a Server Object with an url value of /. @@ -87,9 +88,10 @@ type Document struct { // FindSecurityRequirement will attempt to locate a security requirement string from a supplied name. func (d *Document) FindSecurityRequirement(name string) []low.ValueReference[string] { for k := range d.Security.Value { - for i := range d.Security.Value[k].Value.Requirements.Value { - if i.Value == name { - return d.Security.Value[k].Value.Requirements.Value[i].Value + requirements := d.Security.Value[k].Value.Requirements + for pair := orderedmap.First(requirements.Value); pair != nil; pair = pair.Next() { + if pair.Key().Value == name { + return pair.Value().Value } } } diff --git a/datamodel/low/v3/encoding.go b/datamodel/low/v3/encoding.go index 6e3079f..c965cc9 100644 --- a/datamodel/low/v3/encoding.go +++ b/datamodel/low/v3/encoding.go @@ -6,18 +6,20 @@ package v3 import ( "crypto/sha256" "fmt" + "strings" + "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" - "strings" ) // Encoding represents a low-level OpenAPI 3+ Encoding object // - https://spec.openapis.org/oas/v3.1.0#encoding-object type Encoding struct { ContentType low.NodeReference[string] - Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]] + Headers low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Header]]] Style low.NodeReference[string] Explode low.NodeReference[bool] AllowReserved low.NodeReference[bool] @@ -26,7 +28,7 @@ type Encoding struct { // FindHeader attempts to locate a Header with the supplied name func (en *Encoding) FindHeader(hType string) *low.ValueReference[*Header] { - return low.FindItemInMap[*Header](hType, en.Headers.Value) + return low.FindItemInOrderedMap[*Header](hType, en.Headers.Value) } // Hash will return a consistent SHA256 Hash of the Encoding object @@ -35,18 +37,19 @@ func (en *Encoding) Hash() [32]byte { if en.ContentType.Value != "" { f = append(f, en.ContentType.Value) } - if len(en.Headers.Value) > 0 { - l := make([]string, len(en.Headers.Value)) + if orderedmap.Len(en.Headers.Value) > 0 { + l := make([]string, orderedmap.Len(en.Headers.Value)) keys := make(map[string]low.ValueReference[*Header]) z := 0 - for k := range en.Headers.Value { - keys[k.Value] = en.Headers.Value[k] - l[z] = k.Value + for pair := orderedmap.First(en.Headers.Value); pair != nil; pair = pair.Next() { + keys[pair.Key().Value] = pair.Value() + l[z] = pair.Key().Value z++ } - for k := range en.Headers.Value { - f = append(f, fmt.Sprintf("%s-%x", k.Value, en.Headers.Value[k].Value.Hash())) + // FIXME: Redundant iteration? + for pair := orderedmap.First(en.Headers.Value); pair != nil; pair = pair.Next() { + f = append(f, fmt.Sprintf("%s-%x", pair.Key().Value, pair.Value().Value.Hash())) } } if en.Style.Value != "" { @@ -67,7 +70,7 @@ func (en *Encoding) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return err } if headers != nil { - en.Headers = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]]{ + en.Headers = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Header]]]{ Value: headers, KeyNode: hL, ValueNode: hN, diff --git a/datamodel/low/v3/header.go b/datamodel/low/v3/header.go index 095640f..535406f 100644 --- a/datamodel/low/v3/header.go +++ b/datamodel/low/v3/header.go @@ -6,13 +6,15 @@ package v3 import ( "crypto/sha256" "fmt" + "sort" + "strings" + "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/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" - "strings" ) // Header represents a low-level OpenAPI 3+ Header object. @@ -27,8 +29,8 @@ type Header struct { AllowReserved low.NodeReference[bool] Schema low.NodeReference[*base.SchemaProxy] Example low.NodeReference[any] - Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]] - Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] + Examples low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]] + Content low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference } @@ -40,12 +42,12 @@ func (h *Header) FindExtension(ext string) *low.ValueReference[any] { // FindExample will attempt to locate an Example with a specified name func (h *Header) FindExample(eType string) *low.ValueReference[*base.Example] { - return low.FindItemInMap[*base.Example](eType, h.Examples.Value) + return low.FindItemInOrderedMap[*base.Example](eType, h.Examples.Value) } // FindContent will attempt to locate a MediaType definition, with a specified name func (h *Header) FindContent(ext string) *low.ValueReference[*MediaType] { - return low.FindItemInMap[*MediaType](ext, h.Content.Value) + return low.FindItemInOrderedMap[*MediaType](ext, h.Content.Value) } // GetExtensions returns all Header extensions and satisfies the low.HasExtensions interface. @@ -73,14 +75,14 @@ func (h *Header) Hash() [32]byte { if h.Example.Value != nil { f = append(f, fmt.Sprint(h.Example.Value)) } - if len(h.Examples.Value) > 0 { - for k := range h.Examples.Value { - f = append(f, fmt.Sprintf("%s-%x", k.Value, h.Examples.Value[k].Value.Hash())) + if orderedmap.Len(h.Examples.Value) > 0 { + for pair := orderedmap.First(h.Examples.Value); pair != nil; pair = pair.Next() { + f = append(f, fmt.Sprintf("%s-%x", pair.Key().Value, pair.Value().Value.Hash())) } } - if len(h.Content.Value) > 0 { - for k := range h.Content.Value { - f = append(f, fmt.Sprintf("%s-%x", k.Value, h.Content.Value[k].Value.Hash())) + if orderedmap.Len(h.Content.Value) > 0 { + for pair := orderedmap.First(h.Content.Value); pair != nil; pair = pair.Next() { + f = append(f, fmt.Sprintf("%s-%x", pair.Key().Value, pair.Value().Value.Hash())) } } keys := make([]string, len(h.Extensions)) @@ -113,7 +115,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return eErr } if exps != nil { - h.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]]{ + h.Examples = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]]{ Value: exps, KeyNode: expsL, ValueNode: expsN, @@ -134,7 +136,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error { if cErr != nil { return cErr } - h.Content = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]]{ + h.Content = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]]{ Value: con, KeyNode: cL, ValueNode: cN, diff --git a/datamodel/low/v3/link.go b/datamodel/low/v3/link.go index d5272b9..63e19c5 100644 --- a/datamodel/low/v3/link.go +++ b/datamodel/low/v3/link.go @@ -6,12 +6,14 @@ package v3 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // Link represents a low-level OpenAPI 3+ Link object. @@ -29,7 +31,7 @@ import ( type Link struct { OperationRef low.NodeReference[string] OperationId low.NodeReference[string] - Parameters low.NodeReference[map[low.KeyReference[string]]low.ValueReference[string]] + Parameters low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]] RequestBody low.NodeReference[string] Description low.NodeReference[string] Server low.NodeReference[*Server] @@ -44,7 +46,7 @@ func (l *Link) GetExtensions() map[low.KeyReference[string]]low.ValueReference[a // FindParameter will attempt to locate a parameter string value, using a parameter name input. func (l *Link) FindParameter(pName string) *low.ValueReference[string] { - return low.FindItemInMap[string](pName, l.Parameters.Value) + return low.FindItemInOrderedMap[string](pName, l.Parameters.Value) } // FindExtension will attempt to locate an extension with a specific key @@ -87,10 +89,10 @@ func (l *Link) Hash() [32]byte { } // todo: needs ordering. - keys := make([]string, len(l.Parameters.Value)) + keys := make([]string, orderedmap.Len(l.Parameters.Value)) z := 0 - for k := range l.Parameters.Value { - keys[z] = l.Parameters.Value[k].Value + for pair := orderedmap.First(l.Parameters.Value); pair != nil; pair = pair.Next() { + keys[z] = pair.Value().Value z++ } sort.Strings(keys) diff --git a/datamodel/low/v3/media_type.go b/datamodel/low/v3/media_type.go index 59c7b8e..9671bfe 100644 --- a/datamodel/low/v3/media_type.go +++ b/datamodel/low/v3/media_type.go @@ -6,13 +6,15 @@ package v3 import ( "crypto/sha256" "fmt" + "sort" + "strings" + "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/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" - "strings" ) // MediaType represents a low-level OpenAPI MediaType object. @@ -22,8 +24,8 @@ import ( type MediaType struct { Schema low.NodeReference[*base.SchemaProxy] Example low.NodeReference[any] - Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]] - Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]] + Examples low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]] + Encoding low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Encoding]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference } @@ -40,16 +42,16 @@ func (mt *MediaType) FindExtension(ext string) *low.ValueReference[any] { // FindPropertyEncoding will attempt to locate an Encoding value with a specific name. func (mt *MediaType) FindPropertyEncoding(eType string) *low.ValueReference[*Encoding] { - return low.FindItemInMap[*Encoding](eType, mt.Encoding.Value) + return low.FindItemInOrderedMap[*Encoding](eType, mt.Encoding.Value) } // FindExample will attempt to locate an Example with a specific name. func (mt *MediaType) FindExample(eType string) *low.ValueReference[*base.Example] { - return low.FindItemInMap[*base.Example](eType, mt.Examples.Value) + return low.FindItemInOrderedMap[*base.Example](eType, mt.Examples.Value) } // GetAllExamples will extract all examples from the MediaType instance. -func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueReference[*base.Example] { +func (mt *MediaType) GetAllExamples() orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]] { return mt.Examples.Value } @@ -97,7 +99,7 @@ func (mt *MediaType) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return eErr } if exps != nil { - mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]]{ + mt.Examples = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]]{ Value: exps, KeyNode: expsL, ValueNode: expsN, @@ -110,7 +112,7 @@ func (mt *MediaType) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return encErr } if encs != nil { - mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{ + mt.Encoding = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Encoding]]]{ Value: encs, KeyNode: encsL, ValueNode: encsN, @@ -128,18 +130,18 @@ func (mt *MediaType) Hash() [32]byte { if mt.Example.Value != nil { f = append(f, fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprint(mt.Example.Value))))) } - keys := make([]string, len(mt.Examples.Value)) + keys := make([]string, orderedmap.Len(mt.Examples.Value)) z := 0 - for k := range mt.Examples.Value { - keys[z] = low.GenerateHashString(mt.Examples.Value[k].Value) + for pair := orderedmap.First(mt.Examples.Value); pair != nil; pair = pair.Next() { + keys[z] = low.GenerateHashString(pair.Value().Value) z++ } sort.Strings(keys) f = append(f, keys...) - keys = make([]string, len(mt.Encoding.Value)) + keys = make([]string, orderedmap.Len(mt.Encoding.Value)) z = 0 - for k := range mt.Encoding.Value { - keys[z] = low.GenerateHashString(mt.Encoding.Value[k].Value) + for pair := orderedmap.First(mt.Encoding.Value); pair != nil; pair = pair.Next() { + keys[z] = low.GenerateHashString(pair.Value().Value) } sort.Strings(keys) f = append(f, keys...) diff --git a/datamodel/low/v3/oauth_flows.go b/datamodel/low/v3/oauth_flows.go index cd16da9..247a8a3 100644 --- a/datamodel/low/v3/oauth_flows.go +++ b/datamodel/low/v3/oauth_flows.go @@ -6,12 +6,14 @@ package v3 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // OAuthFlows represents a low-level OpenAPI 3+ OAuthFlows object. @@ -95,7 +97,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 } @@ -107,7 +109,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 @@ -134,10 +136,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 := range o.Scopes.Value { - keys[z] = fmt.Sprintf("%s-%s", k.Value, sha256.Sum256([]byte(fmt.Sprint(o.Scopes.Value[k].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.go b/datamodel/low/v3/operation.go index 909ef8a..ace2481 100644 --- a/datamodel/low/v3/operation.go +++ b/datamodel/low/v3/operation.go @@ -6,13 +6,15 @@ package v3 import ( "crypto/sha256" "fmt" + "sort" + "strings" + "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/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" - "strings" ) // Operation is a low-level representation of an OpenAPI 3+ Operation object. @@ -29,7 +31,7 @@ type Operation struct { Parameters low.NodeReference[[]low.ValueReference[*Parameter]] RequestBody low.NodeReference[*RequestBody] Responses low.NodeReference[*Responses] - Callbacks low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]] + Callbacks low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Callback]]] Deprecated low.NodeReference[bool] Security low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]] Servers low.NodeReference[[]low.ValueReference[*Server]] @@ -39,15 +41,16 @@ type Operation struct { // FindCallback will attempt to locate a Callback instance by the supplied name. func (o *Operation) FindCallback(callback string) *low.ValueReference[*Callback] { - return low.FindItemInMap[*Callback](callback, o.Callbacks.Value) + return low.FindItemInOrderedMap[*Callback](callback, o.Callbacks.Value) } // FindSecurityRequirement will attempt to locate a security requirement string from a supplied name. func (o *Operation) FindSecurityRequirement(name string) []low.ValueReference[string] { for k := range o.Security.Value { - for i := range o.Security.Value[k].Value.Requirements.Value { - if i.Value == name { - return o.Security.Value[k].Value.Requirements.Value[i].Value + requirements := o.Security.Value[k].Value.Requirements + for pair := orderedmap.First(requirements.Value); pair != nil; pair = pair.Next() { + if pair.Key().Value == name { + return pair.Value().Value } } } @@ -101,7 +104,7 @@ func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return cbErr } if callbacks != nil { - o.Callbacks = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]]{ + o.Callbacks = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Callback]]]{ Value: callbacks, KeyNode: cbL, ValueNode: cbN, @@ -202,10 +205,10 @@ func (o *Operation) Hash() [32]byte { sort.Strings(keys) f = append(f, keys...) - keys = make([]string, len(o.Callbacks.Value)) + keys = make([]string, orderedmap.Len(o.Callbacks.Value)) z := 0 - for k := range o.Callbacks.Value { - keys[z] = low.GenerateHashString(o.Callbacks.Value[k].Value) + for pair := orderedmap.First(o.Callbacks.Value); pair != nil; pair = pair.Next() { + keys[z] = low.GenerateHashString(pair.Value().Value) z++ } sort.Strings(keys) @@ -278,6 +281,6 @@ func (o *Operation) GetServers() low.NodeReference[any] { Value: o.Servers.Value, } } -func (o *Operation) GetCallbacks() low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]] { +func (o *Operation) GetCallbacks() low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Callback]]] { return o.Callbacks } diff --git a/datamodel/low/v3/parameter.go b/datamodel/low/v3/parameter.go index 0543f0d..8645f20 100644 --- a/datamodel/low/v3/parameter.go +++ b/datamodel/low/v3/parameter.go @@ -6,13 +6,15 @@ package v3 import ( "crypto/sha256" "fmt" + "sort" + "strings" + "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/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" - "sort" - "strings" ) // Parameter represents a high-level OpenAPI 3+ Parameter object, that is backed by a low-level one. @@ -31,20 +33,20 @@ type Parameter struct { AllowReserved low.NodeReference[bool] Schema low.NodeReference[*base.SchemaProxy] Example low.NodeReference[any] - Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]] - Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] + Examples low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]] + Content low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference } // FindContent will attempt to locate a MediaType instance using the specified name. func (p *Parameter) FindContent(cType string) *low.ValueReference[*MediaType] { - return low.FindItemInMap[*MediaType](cType, p.Content.Value) + return low.FindItemInOrderedMap[*MediaType](cType, p.Content.Value) } // FindExample will attempt to locate a base.Example instance using the specified name. func (p *Parameter) FindExample(eType string) *low.ValueReference[*base.Example] { - return low.FindItemInMap[*base.Example](eType, p.Examples.Value) + return low.FindItemInOrderedMap[*base.Example](eType, p.Examples.Value) } // FindExtension attempts to locate an extension using the specified name. @@ -85,7 +87,7 @@ func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return eErr } if exps != nil { - p.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*base.Example]]{ + p.Examples = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]]{ Value: exps, KeyNode: expsL, ValueNode: expsN, @@ -97,7 +99,7 @@ func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error { if cErr != nil { return cErr } - p.Content = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]]{ + p.Content = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]]{ Value: con, KeyNode: cL, ValueNode: cN, @@ -133,18 +135,18 @@ func (p *Parameter) Hash() [32]byte { } var keys []string - keys = make([]string, len(p.Examples.Value)) + keys = make([]string, orderedmap.Len(p.Examples.Value)) z := 0 - for k := range p.Examples.Value { - keys[z] = low.GenerateHashString(p.Examples.Value[k].Value) + for pair := orderedmap.First(p.Examples.Value); pair != nil; pair = pair.Next() { + keys[z] = low.GenerateHashString(pair.Value().Value) z++ } sort.Strings(keys) f = append(f, keys...) - keys = make([]string, len(p.Content.Value)) + keys = make([]string, orderedmap.Len(p.Content.Value)) z = 0 - for k := range p.Content.Value { - keys[z] = low.GenerateHashString(p.Content.Value[k].Value) + for pair := orderedmap.First(p.Content.Value); pair != nil; pair = pair.Next() { + keys[z] = low.GenerateHashString(pair.Value().Value) z++ } sort.Strings(keys) diff --git a/datamodel/low/v3/paths.go b/datamodel/low/v3/paths.go index a3613ee..129b48d 100644 --- a/datamodel/low/v3/paths.go +++ b/datamodel/low/v3/paths.go @@ -6,7 +6,6 @@ package v3 import ( "crypto/sha256" "fmt" - "io" "sort" "strings" "sync" @@ -33,28 +32,24 @@ type Paths struct { // FindPath will attempt to locate a PathItem using the provided path string. func (p *Paths) FindPath(path string) (result *low.ValueReference[*PathItem]) { - action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { if pair.Key().Value == path { result = pair.ValuePtr() - return io.EOF + break } - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action) return result } // FindPathAndKey attempts to locate a PathItem instance, given a path key. func (p *Paths) FindPathAndKey(path string) (key *low.KeyReference[string], value *low.ValueReference[*PathItem]) { - action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { if pair.Key().Value == path { key = pair.KeyPtr() value = pair.ValuePtr() - return io.EOF + break } - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action) return key, value } @@ -198,14 +193,12 @@ func (p *Paths) Hash() [32]byte { keys := make(map[string]low.ValueReference[*PathItem]) z := 0 - action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error { + for pair := orderedmap.First(p.PathItems); pair != nil; pair = pair.Next() { k := pair.Key().Value keys[k] = pair.Value() l[z] = k z++ - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action) sort.Strings(l) for k := range l { diff --git a/datamodel/low/v3/request_body.go b/datamodel/low/v3/request_body.go index 37952e1..ea681df 100644 --- a/datamodel/low/v3/request_body.go +++ b/datamodel/low/v3/request_body.go @@ -6,19 +6,21 @@ package v3 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // RequestBody represents a low-level OpenAPI 3+ RequestBody object. // - https://spec.openapis.org/oas/v3.1.0#request-body-object type RequestBody struct { Description low.NodeReference[string] - Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] + Content low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]] Required low.NodeReference[bool] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference @@ -36,7 +38,7 @@ func (rb *RequestBody) GetExtensions() map[low.KeyReference[string]]low.ValueRef // FindContent attempts to find content/MediaType defined using a specified name. func (rb *RequestBody) FindContent(cType string) *low.ValueReference[*MediaType] { - return low.FindItemInMap[*MediaType](cType, rb.Content.Value) + return low.FindItemInOrderedMap[*MediaType](cType, rb.Content.Value) } // Build will extract extensions and MediaType objects from the node. @@ -52,7 +54,7 @@ func (rb *RequestBody) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return cErr } if con != nil { - rb.Content = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]]{ + rb.Content = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]]{ Value: con, KeyNode: cL, ValueNode: cN, @@ -70,8 +72,8 @@ func (rb *RequestBody) Hash() [32]byte { if !rb.Required.IsEmpty() { f = append(f, fmt.Sprint(rb.Required.Value)) } - for k := range rb.Content.Value { - f = append(f, low.GenerateHashString(rb.Content.Value[k].Value)) + for pair := orderedmap.First(rb.Content.Value); pair != nil; pair = pair.Next() { + f = append(f, low.GenerateHashString(pair.Value().Value)) } keys := make([]string, len(rb.Extensions)) diff --git a/datamodel/low/v3/response.go b/datamodel/low/v3/response.go index 7c7fdb0..9e8468b 100644 --- a/datamodel/low/v3/response.go +++ b/datamodel/low/v3/response.go @@ -6,12 +6,14 @@ package v3 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // Response represents a high-level OpenAPI 3+ Response object that is backed by a low-level one. @@ -21,10 +23,10 @@ import ( // - https://spec.openapis.org/oas/v3.1.0#response-object type Response struct { Description low.NodeReference[string] - Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]] - Content low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]] + Headers low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Header]]] + Content low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] - Links low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]] + Links low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Link]]] *low.Reference } @@ -40,17 +42,17 @@ func (r *Response) GetExtensions() map[low.KeyReference[string]]low.ValueReferen // FindContent will attempt to locate a MediaType instance using the supplied key. func (r *Response) FindContent(cType string) *low.ValueReference[*MediaType] { - return low.FindItemInMap[*MediaType](cType, r.Content.Value) + return low.FindItemInOrderedMap[*MediaType](cType, r.Content.Value) } // FindHeader will attempt to locate a Header instance using the supplied key. func (r *Response) FindHeader(hType string) *low.ValueReference[*Header] { - return low.FindItemInMap[*Header](hType, r.Headers.Value) + return low.FindItemInOrderedMap[*Header](hType, r.Headers.Value) } // FindLink will attempt to locate a Link instance using the supplied key. func (r *Response) FindLink(hType string) *low.ValueReference[*Link] { - return low.FindItemInMap[*Link](hType, r.Links.Value) + return low.FindItemInOrderedMap[*Link](hType, r.Links.Value) } // Build will extract headers, extensions, content and links from node. @@ -66,7 +68,7 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return err } if headers != nil { - r.Headers = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]]{ + r.Headers = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Header]]]{ Value: headers, KeyNode: lN, ValueNode: kN, @@ -78,7 +80,7 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return cErr } if con != nil { - r.Content = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*MediaType]]{ + r.Content = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]]{ Value: con, KeyNode: clN, ValueNode: cN, @@ -91,7 +93,7 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error { return lErr } if links != nil { - r.Links = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]]{ + r.Links = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*Link]]]{ Value: links, KeyNode: linkLabel, ValueNode: linkValue, @@ -106,26 +108,26 @@ func (r *Response) Hash() [32]byte { if r.Description.Value != "" { f = append(f, r.Description.Value) } - keys := make([]string, len(r.Headers.Value)) + keys := make([]string, orderedmap.Len(r.Headers.Value)) z := 0 - for k := range r.Headers.Value { - keys[z] = fmt.Sprintf("%s-%s", k.Value, low.GenerateHashString(r.Headers.Value[k].Value)) + for pair := orderedmap.First(r.Headers.Value); pair != nil; pair = pair.Next() { + keys[z] = fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)) z++ } sort.Strings(keys) f = append(f, keys...) - keys = make([]string, len(r.Content.Value)) + keys = make([]string, orderedmap.Len(r.Content.Value)) z = 0 - for k := range r.Content.Value { - keys[z] = fmt.Sprintf("%s-%s", k.Value, low.GenerateHashString(r.Content.Value[k].Value)) + for pair := orderedmap.First(r.Content.Value); pair != nil; pair = pair.Next() { + keys[z] = fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)) z++ } sort.Strings(keys) f = append(f, keys...) - keys = make([]string, len(r.Links.Value)) + keys = make([]string, orderedmap.Len(r.Links.Value)) z = 0 - for k := range r.Links.Value { - keys[z] = fmt.Sprintf("%s-%s", k.Value, low.GenerateHashString(r.Links.Value[k].Value)) + for pair := orderedmap.First(r.Links.Value); pair != nil; pair = pair.Next() { + keys[z] = fmt.Sprintf("%s-%s", pair.Key().Value, low.GenerateHashString(pair.Value().Value)) z++ } sort.Strings(keys) diff --git a/datamodel/low/v3/responses.go b/datamodel/low/v3/responses.go index 0089acc..28f8c49 100644 --- a/datamodel/low/v3/responses.go +++ b/datamodel/low/v3/responses.go @@ -6,12 +6,14 @@ package v3 import ( "crypto/sha256" "fmt" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // Responses represents a low-level OpenAPI 3+ Responses object. @@ -33,7 +35,7 @@ import ( // the duplication. Perhaps in the future we could use generics here, but for now to keep things // simple, they are broken out into individual versions. type Responses struct { - Codes map[low.KeyReference[string]]low.ValueReference[*Response] + Codes orderedmap.Map[low.KeyReference[string], low.ValueReference[*Response]] Default low.NodeReference[*Response] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference @@ -75,12 +77,12 @@ func (r *Responses) Build(_, root *yaml.Node, idx *index.SpecIndex) error { } func (r *Responses) getDefault() *low.NodeReference[*Response] { - for n, o := range r.Codes { - if strings.ToLower(n.Value) == DefaultLabel { + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { + if strings.ToLower(pair.Key().Value) == DefaultLabel { return &low.NodeReference[*Response]{ - ValueNode: o.ValueNode, - KeyNode: n.KeyNode, - Value: o.Value, + ValueNode: pair.Value().ValueNode, + KeyNode: pair.Key().KeyNode, + Value: pair.Value().Value, } } } @@ -90,35 +92,33 @@ func (r *Responses) getDefault() *low.NodeReference[*Response] { // used to remove default from codes extracted by Build() func (r *Responses) deleteCode(code string) { var key *low.KeyReference[string] - if r.Codes != nil { - for k := range r.Codes { - if k.Value == code { - key = &k - break - } + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { + if pair.Key().Value == code { + key = pair.KeyPtr() + break } } // should never be nil, but, you never know... science and all that! if key != nil { - delete(r.Codes, *key) + r.Codes.Delete(*key) } } // FindResponseByCode will attempt to locate a Response using an HTTP response code. func (r *Responses) FindResponseByCode(code string) *low.ValueReference[*Response] { - return low.FindItemInMap[*Response](code, r.Codes) + return low.FindItemInOrderedMap[*Response](code, r.Codes) } // Hash will return a consistent SHA256 Hash of the Examples object func (r *Responses) Hash() [32]byte { var f []string var keys []string - keys = make([]string, len(r.Codes)) + keys = make([]string, orderedmap.Len(r.Codes)) cMap := make(map[string]*Response, len(keys)) z := 0 - for k := range r.Codes { - keys[z] = k.Value - cMap[k.Value] = r.Codes[k].Value + for pair := orderedmap.First(r.Codes); pair != nil; pair = pair.Next() { + keys[z] = pair.Key().Value + cMap[pair.Key().Value] = pair.Value().Value z++ } sort.Strings(keys) diff --git a/datamodel/low/v3/server.go b/datamodel/low/v3/server.go index f2a0015..ee2388f 100644 --- a/datamodel/low/v3/server.go +++ b/datamodel/low/v3/server.go @@ -5,12 +5,14 @@ package v3 import ( "crypto/sha256" - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" "sort" "strings" + + "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" ) // Server represents a low-level OpenAPI 3+ Server object. @@ -18,7 +20,7 @@ import ( type Server struct { URL low.NodeReference[string] Description low.NodeReference[string] - Variables low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*ServerVariable]] + Variables low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*ServerVariable]]] Extensions map[low.KeyReference[string]]low.ValueReference[any] *low.Reference } @@ -30,7 +32,7 @@ func (s *Server) GetExtensions() map[low.KeyReference[string]]low.ValueReference // FindVariable attempts to locate a ServerVariable instance using the supplied key. func (s *Server) FindVariable(serverVar string) *low.ValueReference[*ServerVariable] { - return low.FindItemInMap[*ServerVariable](serverVar, s.Variables.Value) + return low.FindItemInOrderedMap[*ServerVariable](serverVar, s.Variables.Value) } // Build will extract server variables from the supplied node. @@ -43,7 +45,7 @@ func (s *Server) Build(_, root *yaml.Node, idx *index.SpecIndex) error { if vars == nil { return nil } - variablesMap := make(map[low.KeyReference[string]]low.ValueReference[*ServerVariable]) + variablesMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*ServerVariable]]() if utils.IsNodeMap(vars) { var currentNode string var keyNode *yaml.Node @@ -56,15 +58,18 @@ func (s *Server) Build(_, root *yaml.Node, idx *index.SpecIndex) error { variable := ServerVariable{} variable.Reference = new(low.Reference) _ = low.BuildModel(varNode, &variable) - variablesMap[low.KeyReference[string]{ - Value: currentNode, - KeyNode: keyNode, - }] = low.ValueReference[*ServerVariable]{ - ValueNode: varNode, - Value: &variable, - } + variablesMap.Set( + low.KeyReference[string]{ + Value: currentNode, + KeyNode: keyNode, + }, + low.ValueReference[*ServerVariable]{ + ValueNode: varNode, + Value: &variable, + }, + ) } - s.Variables = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*ServerVariable]]{ + s.Variables = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*ServerVariable]]]{ KeyNode: kn, ValueNode: vars, Value: variablesMap, @@ -76,10 +81,10 @@ func (s *Server) Build(_, root *yaml.Node, idx *index.SpecIndex) error { // Hash will return a consistent SHA256 Hash of the Server object func (s *Server) Hash() [32]byte { var f []string - keys := make([]string, len(s.Variables.Value)) + keys := make([]string, orderedmap.Len(s.Variables.Value)) z := 0 - for k := range s.Variables.Value { - keys[z] = low.GenerateHashString(s.Variables.Value[k].Value) + for pair := orderedmap.First(s.Variables.Value); pair != nil; pair = pair.Next() { + keys[z] = low.GenerateHashString(pair.Value().Value) z++ } sort.Strings(keys) diff --git a/document_examples_test.go b/document_examples_test.go index 1225726..f2c027c 100644 --- a/document_examples_test.go +++ b/document_examples_test.go @@ -50,7 +50,7 @@ func ExampleNewDocument_fromOpenAPI3Document() { // get a count of the number of paths and schemas. paths := orderedmap.Len(v3Model.Model.Paths.PathItems) - schemas := len(v3Model.Model.Components.Schemas) + schemas := orderedmap.Len(v3Model.Model.Components.Schemas) // print the number of paths and schemas in the document fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) @@ -156,7 +156,7 @@ func ExampleNewDocument_fromSwaggerDocument() { // get a count of the number of paths and schemas. paths := orderedmap.Len(v2Model.Model.Paths.PathItems) - schemas := len(v2Model.Model.Definitions.Definitions) + schemas := orderedmap.Len(v2Model.Model.Definitions.Definitions) // print the number of paths and schemas in the document fmt.Printf("There are %d paths and %d schemas in the document", paths, schemas) @@ -187,7 +187,7 @@ func ExampleNewDocument_fromUnknownVersion() { } if len(errors) <= 0 { paths = orderedmap.Len(v3Model.Model.Paths.PathItems) - schemas = len(v3Model.Model.Components.Schemas) + schemas = orderedmap.Len(v3Model.Model.Components.Schemas) } } if document.GetSpecInfo().SpecType == utils.OpenApi2 { @@ -197,7 +197,7 @@ func ExampleNewDocument_fromUnknownVersion() { } if len(errors) <= 0 { paths = orderedmap.Len(v2Model.Model.Paths.PathItems) - schemas = len(v2Model.Model.Definitions.Definitions) + schemas = orderedmap.Len(v2Model.Model.Definitions.Definitions) } } @@ -562,8 +562,8 @@ components: } // get a reference to SchemaOne and ParameterOne - schemaOne := docModel.Model.Components.Schemas["SchemaOne"].Schema() - parameterOne := docModel.Model.Components.Parameters["ParameterOne"] + schemaOne := docModel.Model.Components.Schemas.GetOrZero("SchemaOne").Schema() + parameterOne := docModel.Model.Components.Parameters.GetOrZero("ParameterOne") // unpack schemaOne extensions into complex `cakes` type schemaOneExtensions, schemaUnpackErrors := high.UnpackExtensions[cakes, *low.Schema](schemaOne) diff --git a/document_test.go b/document_test.go index cc0e0a8..ed9ca30 100644 --- a/document_test.go +++ b/document_test.go @@ -8,11 +8,6 @@ import ( "strings" "testing" - "github.com/pb33f/libopenapi/datamodel" - "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/what-changed/model" - "github.com/stretchr/testify/assert" - "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/high/base" v3high "github.com/pb33f/libopenapi/datamodel/high/v3" @@ -254,17 +249,18 @@ func TestDocument_RenderAndReload(t *testing.T) { // mutate the model h := m.Model h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.OperationId = "findACakeInABakery" - h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.Responses.Codes["400"].Description = "a nice bucket of mice" + h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.Responses.Codes.GetOrZero("400").Description = "a nice bucket of mice" h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags = append(h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags, "gurgle", "giggle") h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security = append(h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security, - &base.SecurityRequirement{Requirements: map[string][]string{ + &base.SecurityRequirement{Requirements: orderedmap.ToOrderedMap(map[string][]string{ "pizza-and-cake": {"read:abook", "write:asong"}, - }}) + })}, + ) - h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example = "I am a teapot, filled with love." - h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl = "https://pb33f.io" + h.Components.Schemas.GetOrZero("Order").Schema().Properties.GetOrZero("status").Schema().Example = "I am a teapot, filled with love." + h.Components.SecuritySchemes.GetOrZero("petstore_auth").Flows.Implicit.AuthorizationUrl = "https://pb33f.io" bytes, _, newDocModel, e := doc.RenderAndReload() assert.Nil(t, e) @@ -273,17 +269,17 @@ func TestDocument_RenderAndReload(t *testing.T) { h = newDocModel.Model assert.Equal(t, "findACakeInABakery", h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.OperationId) assert.Equal(t, "a nice bucket of mice", - h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.Responses.Codes["400"].Description) + h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.Responses.Codes.GetOrZero("400").Description) assert.Len(t, h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags, 3) assert.Len(t, h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags, 3) yu := h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security - assert.Equal(t, "read:abook", yu[len(yu)-1].Requirements["pizza-and-cake"][0]) + assert.Equal(t, "read:abook", yu[len(yu)-1].Requirements.GetOrZero("pizza-and-cake")[0]) assert.Equal(t, "I am a teapot, filled with love.", - h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example) + h.Components.Schemas.GetOrZero("Order").Schema().Properties.GetOrZero("status").Schema().Example) assert.Equal(t, "https://pb33f.io", - h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl) + h.Components.SecuritySchemes.GetOrZero("petstore_auth").Flows.Implicit.AuthorizationUrl) } func TestDocument_Render(t *testing.T) { @@ -304,18 +300,20 @@ func TestDocument_Render(t *testing.T) { // mutate the model h := m.Model - h.Paths.PathItems["/pet/findByStatus"].Get.OperationId = "findACakeInABakery" - h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description = "a nice bucket of mice" - h.Paths.PathItems["/pet/findByTags"].Get.Tags = - append(h.Paths.PathItems["/pet/findByTags"].Get.Tags, "gurgle", "giggle") + h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.OperationId = "findACakeInABakery" + h.Paths.PathItems.GetOrZero("/pet/findByStatus"). + Get.Responses.Codes.GetOrZero("400").Description = "a nice bucket of mice" + h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags = + append(h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags, "gurgle", "giggle") - h.Paths.PathItems["/pet/{petId}"].Delete.Security = append(h.Paths.PathItems["/pet/{petId}"].Delete.Security, - &base.SecurityRequirement{Requirements: map[string][]string{ + h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security = append(h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security, + &base.SecurityRequirement{Requirements: orderedmap.ToOrderedMap(map[string][]string{ "pizza-and-cake": {"read:abook", "write:asong"}, - }}) + })}, + ) - h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example = "I am a teapot, filled with love." - h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl = "https://pb33f.io" + h.Components.Schemas.GetOrZero("Order").Schema().Properties.GetOrZero("status").Schema().Example = "I am a teapot, filled with love." + h.Components.SecuritySchemes.GetOrZero("petstore_auth").Flows.Implicit.AuthorizationUrl = "https://pb33f.io" bytes, e := doc.Render() assert.NoError(t, e) @@ -329,19 +327,19 @@ func TestDocument_Render(t *testing.T) { assert.Len(t, docErrs, 0) h = newDocModel.Model - assert.Equal(t, "findACakeInABakery", h.Paths.PathItems["/pet/findByStatus"].Get.OperationId) + assert.Equal(t, "findACakeInABakery", h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.OperationId) assert.Equal(t, "a nice bucket of mice", - h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description) - assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3) + h.Paths.PathItems.GetOrZero("/pet/findByStatus").Get.Responses.Codes.GetOrZero("400").Description) + assert.Len(t, h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags, 3) - assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3) - yu := h.Paths.PathItems["/pet/{petId}"].Delete.Security - assert.Equal(t, "read:abook", yu[len(yu)-1].Requirements["pizza-and-cake"][0]) + assert.Len(t, h.Paths.PathItems.GetOrZero("/pet/findByTags").Get.Tags, 3) + yu := h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security + assert.Equal(t, "read:abook", yu[len(yu)-1].Requirements.GetOrZero("pizza-and-cake")[0]) assert.Equal(t, "I am a teapot, filled with love.", - h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example) + h.Components.Schemas.GetOrZero("Order").Schema().Properties.GetOrZero("status").Schema().Example) assert.Equal(t, "https://pb33f.io", - h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl) + h.Components.SecuritySchemes.GetOrZero("petstore_auth").Flows.Implicit.AuthorizationUrl) } func TestDocument_RenderWithLargeIndention(t *testing.T) { @@ -354,7 +352,6 @@ func TestDocument_RenderWithLargeIndention(t *testing.T) { doc.BuildV3Model() bytes, _ := doc.Render() assert.Equal(t, json, string(bytes)) - } func TestDocument_Render_ChangeCheck_Burgershop(t *testing.T) { @@ -566,16 +563,16 @@ func TestSchemaRefIsFollowed(t *testing.T) { // get a count of the number of paths and schemas. schemas := v3Model.Model.Components.Schemas - assert.Equal(t, 4, len(schemas)) + assert.Equal(t, 4, orderedmap.Len(schemas)) - fp := schemas["FP"] - fbsref := schemas["FBSRef"] + fp := schemas.GetOrZero("FP") + fbsref := schemas.GetOrZero("FBSRef") assert.Equal(t, fp.Schema().Pattern, fbsref.Schema().Pattern) assert.Equal(t, fp.Schema().Example, fbsref.Schema().Example) - byte := schemas["Byte"] - uint64 := schemas["UInt64"] + byte := schemas.GetOrZero("Byte") + uint64 := schemas.GetOrZero("UInt64") assert.Equal(t, uint64.Schema().Format, byte.Schema().Format) assert.Equal(t, uint64.Schema().Type, byte.Schema().Type) @@ -674,7 +671,8 @@ components: example: "4578152156": Not Started "5678904321": On Hold - type: object` + type: object +` doc, err := NewDocument([]byte(d)) if err != nil { @@ -689,7 +687,7 @@ components: // render the document. rend, _ := result.Model.Render() - assert.Len(t, rend, 644) + assert.Equal(t, d, string(rend)) } func TestDocument_OperationsAsRefs(t *testing.T) { @@ -897,12 +895,11 @@ func TestDocument_Render_PreserveOrder(t *testing.T) { require.Equal(t, pathCount, orderedmap.Len(pathItems)) var i int - _ = orderedmap.For(model.Model.Paths.PathItems, func(pair orderedmap.Pair[string, *v3high.PathItem]) error { + for pair := orderedmap.First(model.Model.Paths.PathItems); pair != nil; pair = pair.Next() { pathName := fmt.Sprintf("/foobar/%d", i) assert.Equal(t, pathName, pair.Key()) i++ - return nil - }) + } assert.Equal(t, pathCount, i) } diff --git a/orderedmap/orderedmap.go b/orderedmap/orderedmap.go index 83d4ea5..86ab883 100644 --- a/orderedmap/orderedmap.go +++ b/orderedmap/orderedmap.go @@ -72,6 +72,11 @@ func (o *wrapOrderedMap[K, V]) First() Pair[K, V] { } } +// IsZero is required to support `omitempty` tag for YAML/JSON marshaling. +func (o *wrapOrderedMap[K, V]) IsZero() bool { + return o.Len() == 0 +} + func (p *wrapPair[K, V]) Next() Pair[K, V] { next := p.Pair.Next() if next == nil { @@ -140,23 +145,13 @@ func ToOrderedMap[K comparable, V any](m map[K]V) Map[K, V] { return om } -// For iterates a `Map` and calls action() on each map pair. -// action() may return `io.EOF` to break iteration. +// First returns map's first pair for iteration. // Safely handles nil pointer. -func For[K comparable, V any](m Map[K, V], action ActionFunc[K, V]) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - c := Iterate(ctx, m) - for pair := range c { - err := action(pair) - if err == io.EOF { - return nil - } - if err != nil { - return err - } +func First[K comparable, V any](m Map[K, V]) Pair[K, V] { + if m == nil { + return nil } - return nil + return m.First() } // TranslateMapParallel iterates a `Map` in parallel and calls translate() diff --git a/orderedmap/orderedmap_test.go b/orderedmap/orderedmap_test.go index 7c20a89..8f4d6f0 100644 --- a/orderedmap/orderedmap_test.go +++ b/orderedmap/orderedmap_test.go @@ -159,68 +159,6 @@ func TestMap(t *testing.T) { }) }) - t.Run("For()", func(t *testing.T) { - const mapSize = 10 - - t.Run("Nil pointer", func(t *testing.T) { - var m orderedmap.Map[string, int] - err := orderedmap.For(m, func(_ orderedmap.Pair[string, int]) error { - return errors.New("Expected no data") - }) - require.NoError(t, err) - }) - - t.Run("Empty", func(t *testing.T) { - m := orderedmap.New[string, int]() - err := orderedmap.For(m, func(_ orderedmap.Pair[string, int]) error { - return errors.New("Expected no data") - }) - require.NoError(t, err) - }) - - t.Run("Full iteration", func(t *testing.T) { - m := orderedmap.New[string, int]() - for i := 0; i < mapSize; i++ { - m.Set(fmt.Sprintf("key%d", i), i+1000) - } - - var i int - err := orderedmap.For(m, func(pair orderedmap.Pair[string, int]) error { - assert.Equal(t, fmt.Sprintf("key%d", i), pair.Key()) - assert.Equal(t, fmt.Sprintf("key%d", i), *pair.KeyPtr()) - assert.Equal(t, i+1000, pair.Value()) - assert.Equal(t, i+1000, *pair.ValuePtr()) - i++ - require.LessOrEqual(t, i, mapSize) - return nil - }) - require.NoError(t, err) - assert.Equal(t, mapSize, i) - }) - - t.Run("Partial iteration", func(t *testing.T) { - m := orderedmap.New[string, int]() - for i := 0; i < mapSize; i++ { - m.Set(fmt.Sprintf("key%d", i), i+1000) - } - - var i int - err := orderedmap.For(m, func(pair orderedmap.Pair[string, int]) error { - assert.Equal(t, fmt.Sprintf("key%d", i), pair.Key()) - assert.Equal(t, fmt.Sprintf("key%d", i), *pair.KeyPtr()) - assert.Equal(t, i+1000, pair.Value()) - assert.Equal(t, i+1000, *pair.ValuePtr()) - i++ - if i >= mapSize/2 { - return io.EOF - } - return nil - }) - require.NoError(t, err) - assert.Equal(t, mapSize/2, i) - }) - }) - t.Run("TranslateMapParallel()", func(t *testing.T) { const mapSize = 1000 diff --git a/renderer/mock_generator_examples_test.go b/renderer/mock_generator_examples_test.go index b9acc5a..58fe5c3 100644 --- a/renderer/mock_generator_examples_test.go +++ b/renderer/mock_generator_examples_test.go @@ -5,8 +5,9 @@ package renderer import ( "fmt" - "github.com/pb33f/libopenapi" "os" + + "github.com/pb33f/libopenapi" ) func ExampleMockGenerator_generateBurgerMock_yaml() { @@ -21,7 +22,7 @@ func ExampleMockGenerator_generateBurgerMock_yaml() { v3Model, _ := document.BuildV3Model() // create a mock of the Burger model - burgerModel := v3Model.Model.Components.Schemas["Burger"] + burgerModel := v3Model.Model.Components.Schemas.GetOrZero("Burger") burger := burgerModel.Schema() mock, err := mg.GenerateMock(burger, "") @@ -45,7 +46,7 @@ func ExampleMockGenerator_generateFriesMock_json() { v3Model, _ := document.BuildV3Model() // create a mock of the Fries model - friesModel := v3Model.Model.Components.Schemas["Fries"] + friesModel := v3Model.Model.Components.Schemas.GetOrZero("Fries") fries := friesModel.Schema() mock, err := mg.GenerateMock(fries, "") @@ -68,7 +69,8 @@ func ExampleMockGenerator_generateRequestMock_json() { v3Model, _ := document.BuildV3Model() // create a mock of the burger request model, extracted from the operation directly. - burgerRequestModel := v3Model.Model.Paths.PathItems["/burgers"].Post.RequestBody.Content["application/json"] + burgerRequestModel := v3Model.Model.Paths.PathItems.GetOrZero("/burgers"). + Post.RequestBody.Content.GetOrZero("application/json") // use the 'cakeBurger' example to generate a mock mock, err := mg.GenerateMock(burgerRequestModel, "cakeBurger") @@ -92,7 +94,8 @@ func ExampleMockGenerator_generateResponseMock_json() { v3Model, _ := document.BuildV3Model() // create a mock of the burger response model, extracted from the operation directly. - burgerResponseModel := v3Model.Model.Paths.PathItems["/burgers"].Post.Responses.Codes["200"].Content["application/json"] + burgerResponseModel := v3Model.Model.Paths.PathItems.GetOrZero("/burgers"). + Post.Responses.Codes.GetOrZero("200").Content.GetOrZero("application/json") // use the 'filetOFish' example to generate a mock mock, err := mg.GenerateMock(burgerResponseModel, "filetOFish") @@ -116,7 +119,7 @@ func ExampleMockGenerator_generatePolymorphicMock_json() { v3Model, _ := document.BuildV3Model() // create a mock of the SomePayload component, which uses polymorphism (incorrectly) - payloadModel := v3Model.Model.Components.Schemas["SomePayload"] + payloadModel := v3Model.Model.Components.Schemas.GetOrZero("SomePayload") payload := payloadModel.Schema() mock, err := mg.GenerateMock(payload, "") diff --git a/renderer/schema_renderer.go b/renderer/schema_renderer.go index b6aeb13..39135bb 100644 --- a/renderer/schema_renderer.go +++ b/renderer/schema_renderer.go @@ -7,14 +7,16 @@ import ( cryptoRand "crypto/rand" "encoding/base64" "fmt" - "github.com/lucasjones/reggen" - "github.com/pb33f/libopenapi/datamodel/high/base" - "golang.org/x/exp/slices" "io" "math/rand" "os" "strings" "time" + + "github.com/lucasjones/reggen" + "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/orderedmap" + "golang.org/x/exp/slices" ) const rootType = "rootType" @@ -217,16 +219,17 @@ func (wr *SchemaRenderer) DiveIntoSchema(schema *base.Schema, key string, struct if properties != nil { // check if this schema has required properties, if so, then only render required props, if not // render everything in the schema. - checkProps := make(map[string]*base.SchemaProxy) + checkProps := orderedmap.New[string, *base.SchemaProxy]() if len(schema.Required) > 0 { for _, requiredProp := range schema.Required { - checkProps[requiredProp] = properties[requiredProp] + checkProps.Set(requiredProp, properties.GetOrZero(requiredProp)) } } else { checkProps = properties } - for propName, propValue := range checkProps { + for pair := orderedmap.First(checkProps); pair != nil; pair = pair.Next() { // render property + propName, propValue := pair.Key(), pair.Value() propertySchema := propValue.Schema() wr.DiveIntoSchema(propertySchema, propName, propertyMap, depth+1) } @@ -249,8 +252,9 @@ func (wr *SchemaRenderer) DiveIntoSchema(schema *base.Schema, key string, struct dependentSchemas := schema.DependentSchemas if dependentSchemas != nil { dependentSchemasMap := make(map[string]any) - for k, dependentSchema := range dependentSchemas { + for pair := orderedmap.First(dependentSchemas); pair != nil; pair = pair.Next() { // only map if the property exists + k, dependentSchema := pair.Key(), pair.Value() if propertyMap[k] != nil { dependentSchemaCompiled := dependentSchema.Schema() wr.DiveIntoSchema(dependentSchemaCompiled, k, dependentSchemasMap, depth+1) diff --git a/renderer/schema_renderer_test.go b/renderer/schema_renderer_test.go index 873940b..b9acc01 100644 --- a/renderer/schema_renderer_test.go +++ b/renderer/schema_renderer_test.go @@ -8,17 +8,18 @@ import ( "encoding/json" "errors" "fmt" - highbase "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/datamodel/low" - lowbase "github.com/pb33f/libopenapi/datamodel/low/base" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" "net/url" "os" "regexp" "strings" "testing" "time" + + highbase "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/datamodel/low" + lowbase "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" ) func TestRenderSchema(t *testing.T) { @@ -1144,7 +1145,7 @@ properties: loopMe = func(parent *highbase.SchemaProxy, level int) { schemaProxy := buildSchema() if parent != nil { - parent.Schema().Properties["child"] = schemaProxy + parent.Schema().Properties.Set("child", schemaProxy) } if level < 110 { loopMe(schemaProxy, level+1) diff --git a/what-changed/model/callback.go b/what-changed/model/callback.go index ca554d3..f018138 100644 --- a/what-changed/model/callback.go +++ b/what-changed/model/callback.go @@ -5,7 +5,8 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/datamodel/low/v3" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" ) // CallbackChanges represents all changes made between two Callback OpenAPI objects. @@ -65,14 +66,14 @@ func CompareCallback(l, r *v3.Callback) *CallbackChanges { lValues := make(map[string]low.ValueReference[*v3.PathItem]) rValues := make(map[string]low.ValueReference[*v3.PathItem]) - for k := range l.Expression.Value { - lHashes[k.Value] = low.GenerateHashString(l.Expression.Value[k].Value) - lValues[k.Value] = l.Expression.Value[k] + for pair := orderedmap.First(l.Expression.Value); pair != nil; pair = pair.Next() { + lHashes[pair.Key().Value] = low.GenerateHashString(pair.Value().Value) + lValues[pair.Key().Value] = pair.Value() } - for k := range r.Expression.Value { - rHashes[k.Value] = low.GenerateHashString(r.Expression.Value[k].Value) - rValues[k.Value] = r.Expression.Value[k] + for pair := orderedmap.First(r.Expression.Value); pair != nil; pair = pair.Next() { + rHashes[pair.Key().Value] = low.GenerateHashString(pair.Value().Value) + rValues[pair.Key().Value] = pair.Value() } expChanges := make(map[string]*PathItemChanges) diff --git a/what-changed/model/comparison_functions.go b/what-changed/model/comparison_functions.go index 94fdd71..0a732e1 100644 --- a/what-changed/model/comparison_functions.go +++ b/what-changed/model/comparison_functions.go @@ -5,11 +5,13 @@ package model import ( "fmt" - "github.com/pb33f/libopenapi/utils" "reflect" "strings" "sync" + "github.com/pb33f/libopenapi/orderedmap" + "github.com/pb33f/libopenapi/utils" + "github.com/pb33f/libopenapi/datamodel/low" "gopkg.in/yaml.v3" ) @@ -235,12 +237,12 @@ 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 map[low.KeyReference[string]]low.ValueReference[T], +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 { return CheckMapForChangesWithComp(expLeft, expRight, changes, label, compareFunc, true) } -func CheckMapForAdditionRemoval[T any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T], +func CheckMapForAdditionRemoval[T any](expLeft, expRight orderedmap.Map[low.KeyReference[string], low.ValueReference[T]], changes *[]*Change, label string) any { // do nothing doNothing := func(l, r T) any { @@ -263,7 +265,7 @@ func CheckMapForAdditionRemoval[T any](expLeft, expRight map[low.KeyReference[st // CheckMapForChangesWithComp 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. The compare // bit determines if the comparison should be run or not. -func CheckMapForChangesWithComp[T any, R any](expLeft, expRight map[low.KeyReference[string]]low.ValueReference[T], +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 { // stop concurrent threads screwing up changes. @@ -274,14 +276,16 @@ func CheckMapForChangesWithComp[T any, R any](expLeft, expRight map[low.KeyRefer lValues := make(map[string]low.ValueReference[T]) rValues := make(map[string]low.ValueReference[T]) - for k := range expLeft { - lHashes[k.Value] = low.GenerateHashString(expLeft[k].Value) - lValues[k.Value] = expLeft[k] + for pair := orderedmap.First(expLeft); pair != nil; pair = pair.Next() { + k := pair.Key() + lHashes[k.Value] = low.GenerateHashString(pair.Value().Value) + lValues[k.Value] = pair.Value() } - for k := range expRight { - rHashes[k.Value] = low.GenerateHashString(expRight[k].Value) - rValues[k.Value] = expRight[k] + for pair := orderedmap.First(expRight); pair != nil; pair = pair.Next() { + k := pair.Key() + rHashes[k.Value] = low.GenerateHashString(pair.Value().Value) + rValues[k.Value] = pair.Value() } expChanges := make(map[string]R) diff --git a/what-changed/model/components.go b/what-changed/model/components.go index 220e4e5..025b4a4 100644 --- a/what-changed/model/components.go +++ b/what-changed/model/components.go @@ -10,6 +10,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/low/base" v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" ) // ComponentsChanges represents changes made to both OpenAPI and Swagger documents. This model is based on OpenAPI 3 @@ -55,7 +56,7 @@ func CompareComponents(l, r any) *ComponentsChanges { reflect.TypeOf(&v2.ParameterDefinitions{}) == reflect.TypeOf(r) { lDef := l.(*v2.ParameterDefinitions) rDef := r.(*v2.ParameterDefinitions) - var a, b map[low.KeyReference[string]]low.ValueReference[*v2.Parameter] + var a, b orderedmap.Map[low.KeyReference[string], low.ValueReference[*v2.Parameter]] if lDef != nil { a = lDef.Definitions } @@ -70,7 +71,7 @@ func CompareComponents(l, r any) *ComponentsChanges { reflect.TypeOf(&v2.ResponsesDefinitions{}) == reflect.TypeOf(r) { lDef := l.(*v2.ResponsesDefinitions) rDef := r.(*v2.ResponsesDefinitions) - var a, b map[low.KeyReference[string]]low.ValueReference[*v2.Response] + var a, b orderedmap.Map[low.KeyReference[string], low.ValueReference[*v2.Response]] if lDef != nil { a = lDef.Definitions } @@ -85,7 +86,7 @@ func CompareComponents(l, r any) *ComponentsChanges { reflect.TypeOf(&v2.Definitions{}) == reflect.TypeOf(r) { lDef := l.(*v2.Definitions) rDef := r.(*v2.Definitions) - var a, b map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy] + var a, b orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]] if lDef != nil { a = lDef.Schemas } @@ -100,7 +101,7 @@ func CompareComponents(l, r any) *ComponentsChanges { reflect.TypeOf(&v2.SecurityDefinitions{}) == reflect.TypeOf(r) { lDef := l.(*v2.SecurityDefinitions) rDef := r.(*v2.SecurityDefinitions) - var a, b map[low.KeyReference[string]]low.ValueReference[*v2.SecurityScheme] + var a, b orderedmap.Map[low.KeyReference[string], low.ValueReference[*v2.SecurityScheme]] if lDef != nil { a = lDef.Definitions } @@ -217,7 +218,7 @@ type componentComparison struct { } // run a generic comparison in a thread which in turn splits checks into further threads. -func runComparison[T any, R any](l, r map[low.KeyReference[string]]low.ValueReference[T], +func runComparison[T any, R any](l, r orderedmap.Map[low.KeyReference[string], low.ValueReference[T]], changes *[]*Change, label string, compareFunc func(l, r T) R, doneChan chan componentComparison) { // for schemas diff --git a/what-changed/model/examples.go b/what-changed/model/examples.go index 35539e4..0775308 100644 --- a/what-changed/model/examples.go +++ b/what-changed/model/examples.go @@ -6,6 +6,7 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" v2 "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/orderedmap" ) // ExamplesChanges represents changes made between Swagger Examples objects (Not OpenAPI 3). @@ -37,14 +38,14 @@ func CompareExamplesV2(l, r *v2.Examples) *ExamplesChanges { lValues := make(map[string]low.ValueReference[any]) rValues := make(map[string]low.ValueReference[any]) - for k := range l.Values { - lHashes[k.Value] = low.GenerateHashString(l.Values[k].Value) - lValues[k.Value] = l.Values[k] + for pair := orderedmap.First(l.Values); pair != nil; pair = pair.Next() { + lHashes[pair.Key().Value] = low.GenerateHashString(pair.Value().Value) + lValues[pair.Key().Value] = pair.Value() } - for k := range r.Values { - rHashes[k.Value] = low.GenerateHashString(r.Values[k].Value) - rValues[k.Value] = r.Values[k] + for pair := orderedmap.First(r.Values); pair != nil; pair = pair.Next() { + rHashes[pair.Key().Value] = low.GenerateHashString(pair.Value().Value) + rValues[pair.Key().Value] = pair.Value() } var changes []*Change diff --git a/what-changed/model/link.go b/what-changed/model/link.go index 5a47ac6..cc87012 100644 --- a/what-changed/model/link.go +++ b/what-changed/model/link.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" ) // LinkChanges represent changes made between two OpenAPI Link Objects. @@ -127,11 +128,11 @@ func CompareLinks(l, r *v3.Link) *LinkChanges { // parameters lValues := make(map[string]low.ValueReference[string]) rValues := make(map[string]low.ValueReference[string]) - for i := range l.Parameters.Value { - lValues[i.Value] = l.Parameters.Value[i] + for pair := orderedmap.First(l.Parameters.Value); pair != nil; pair = pair.Next() { + lValues[pair.Key().Value] = pair.Value() } - for i := range r.Parameters.Value { - rValues[i.Value] = r.Parameters.Value[i] + for pair := orderedmap.First(r.Parameters.Value); pair != nil; pair = pair.Next() { + rValues[pair.Key().Value] = pair.Value() } for k := range lValues { if _, ok := rValues[k]; !ok { diff --git a/what-changed/model/oauth_flows.go b/what-changed/model/oauth_flows.go index 6b3410b..bb71edf 100644 --- a/what-changed/model/oauth_flows.go +++ b/what-changed/model/oauth_flows.go @@ -5,7 +5,8 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/datamodel/low/v3" + 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 v := range l.Scopes.Value { - if r != nil && r.FindScope(v.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, - l.Scopes.Value[v].ValueNode, nil, true, - v.Value, nil) + pair.Value().ValueNode, nil, true, + pair.Key().Value, nil) continue } - if r != nil && r.FindScope(v.Value) != nil { - if l.Scopes.Value[v].Value != r.FindScope(v.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, - l.Scopes.Value[v].ValueNode, r.FindScope(v.Value).ValueNode, true, - l.Scopes.Value[v].Value, r.FindScope(v.Value).Value) + pair.Value().ValueNode, r.FindScope(pair.Key().Value).ValueNode, true, + pair.Value().Value, r.FindScope(pair.Key().Value).Value) } } } - for v := range r.Scopes.Value { - if l != nil && l.FindScope(v.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, r.Scopes.Value[v].ValueNode, false, - nil, v.Value) + nil, pair.Value().ValueNode, false, + nil, pair.Key().Value) } } oa := new(OAuthFlowChanges) diff --git a/what-changed/model/paths.go b/what-changed/model/paths.go index a6386ac..e86270e 100644 --- a/what-changed/model/paths.go +++ b/what-changed/model/paths.go @@ -77,16 +77,12 @@ func ComparePaths(l, r any) *PathsChanges { lKeys := make(map[string]low.ValueReference[*v2.PathItem]) rKeys := make(map[string]low.ValueReference[*v2.PathItem]) - laction := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v2.PathItem]]) error { + for pair := orderedmap.First(lPath.PathItems); pair != nil; pair = pair.Next() { lKeys[pair.Key().Value] = pair.Value() - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*v2.PathItem]](lPath.PathItems, laction) - raction := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v2.PathItem]]) error { + for pair := orderedmap.First(rPath.PathItems); pair != nil; pair = pair.Next() { rKeys[pair.Key().Value] = pair.Value() - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*v2.PathItem]](rPath.PathItems, raction) // run every comparison in a thread. var mLock sync.Mutex @@ -152,16 +148,12 @@ func ComparePaths(l, r any) *PathsChanges { lKeys := make(map[string]low.ValueReference[*v3.PathItem]) rKeys := make(map[string]low.ValueReference[*v3.PathItem]) - laction := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v3.PathItem]]) error { + for pair := orderedmap.First(lPath.PathItems); pair != nil; pair = pair.Next() { lKeys[pair.Key().Value] = pair.Value() - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*v3.PathItem]](lPath.PathItems, laction) - raction := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v3.PathItem]]) error { + for pair := orderedmap.First(rPath.PathItems); pair != nil; pair = pair.Next() { rKeys[pair.Key().Value] = pair.Value() - return nil } - _ = orderedmap.For[low.KeyReference[string], low.ValueReference[*v3.PathItem]](rPath.PathItems, raction) // run every comparison in a thread. var mLock sync.Mutex diff --git a/what-changed/model/schema.go b/what-changed/model/schema.go index 7d4b80c..2f2045b 100644 --- a/what-changed/model/schema.go +++ b/what-changed/model/schema.go @@ -5,14 +5,15 @@ package model import ( "fmt" - "golang.org/x/exp/slices" "sort" "sync" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "github.com/pb33f/libopenapi/utils" + "golang.org/x/exp/slices" "gopkg.in/yaml.v3" ) @@ -424,7 +425,7 @@ func checkSchemaXML(lSchema *base.Schema, rSchema *base.Schema, changes *[]*Chan func checkMappedSchemaOfASchema( lSchema, - rSchema map[low.KeyReference[string]]low.ValueReference[*base.SchemaProxy], + rSchema orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.SchemaProxy]], changes *[]*Change, doneChan chan bool, ) (map[string]*SchemaChanges, int) { @@ -437,15 +438,15 @@ func checkMappedSchemaOfASchema( rEntities := make(map[string]*base.SchemaProxy) rKeyNodes := make(map[string]*yaml.Node) - for w := range lSchema { - lProps = append(lProps, w.Value) - lEntities[w.Value] = lSchema[w].Value - lKeyNodes[w.Value] = w.KeyNode + for pair := orderedmap.First(lSchema); pair != nil; pair = pair.Next() { + lProps = append(lProps, pair.Key().Value) + lEntities[pair.Key().Value] = pair.Value().Value + lKeyNodes[pair.Key().Value] = pair.Key().KeyNode } - for w := range rSchema { - rProps = append(rProps, w.Value) - rEntities[w.Value] = rSchema[w].Value - rKeyNodes[w.Value] = w.KeyNode + for pair := orderedmap.First(rSchema); pair != nil; pair = pair.Next() { + rProps = append(rProps, pair.Key().Value) + rEntities[pair.Key().Value] = pair.Value().Value + rKeyNodes[pair.Key().Value] = pair.Key().KeyNode } sort.Strings(lProps) sort.Strings(rProps) diff --git a/what-changed/model/scopes.go b/what-changed/model/scopes.go index 341ccaf..1f9882f 100644 --- a/what-changed/model/scopes.go +++ b/what-changed/model/scopes.go @@ -5,8 +5,9 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/datamodel/low/v2" + v2 "github.com/pb33f/libopenapi/datamodel/low/v2" v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" ) // ScopesChanges represents changes between two Swagger Scopes Objects @@ -46,26 +47,26 @@ func CompareScopes(l, r *v2.Scopes) *ScopesChanges { return nil } var changes []*Change - for v := range l.Values { - if r != nil && r.FindScope(v.Value) == nil { + for pair := orderedmap.First(l.Values); pair != nil; pair = pair.Next() { + if r != nil && r.FindScope(pair.Key().Value) == nil { CreateChange(&changes, ObjectRemoved, v3.Scopes, - l.Values[v].ValueNode, nil, true, - v.Value, nil) + pair.Value().ValueNode, nil, true, + pair.Key().Value, nil) continue } - if r != nil && r.FindScope(v.Value) != nil { - if l.Values[v].Value != r.FindScope(v.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, - l.Values[v].ValueNode, r.FindScope(v.Value).ValueNode, true, - l.Values[v].Value, r.FindScope(v.Value).Value) + pair.Value().ValueNode, r.FindScope(pair.Key().Value).ValueNode, true, + pair.Value().Value, r.FindScope(pair.Key().Value).Value) } } } - for v := range r.Values { - if l != nil && l.FindScope(v.Value) == nil { + for pair := orderedmap.First(r.Values); pair != nil; pair = pair.Next() { + if l != nil && l.FindScope(pair.Key().Value) == nil { CreateChange(&changes, ObjectAdded, v3.Scopes, - nil, r.Values[v].ValueNode, false, - nil, v.Value) + nil, pair.Value().ValueNode, false, + nil, pair.Key().Value) } } diff --git a/what-changed/model/security_requirement.go b/what-changed/model/security_requirement.go index 6794f72..b6b98a6 100644 --- a/what-changed/model/security_requirement.go +++ b/what-changed/model/security_requirement.go @@ -6,7 +6,8 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low/base" - "github.com/pb33f/libopenapi/datamodel/low/v3" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/orderedmap" "gopkg.in/yaml.v3" ) @@ -56,22 +57,22 @@ func addedSecurityRequirement(vn *yaml.Node, name string, changes *[]*Change) { } // tricky to do this correctly, this is my solution. -func checkSecurityRequirement(lSec, rSec map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]], +func checkSecurityRequirement(lSec, rSec orderedmap.Map[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]], changes *[]*Change) { - lKeys := make([]string, len(lSec)) - rKeys := make([]string, len(rSec)) + lKeys := make([]string, orderedmap.Len(lSec)) + rKeys := make([]string, orderedmap.Len(rSec)) lValues := make(map[string]low.ValueReference[[]low.ValueReference[string]]) rValues := make(map[string]low.ValueReference[[]low.ValueReference[string]]) var n, z int - for i := range lSec { - lKeys[n] = i.Value - lValues[i.Value] = lSec[i] + for pair := orderedmap.First(lSec); pair != nil; pair = pair.Next() { + lKeys[n] = pair.Key().Value + lValues[pair.Key().Value] = pair.Value() n++ } - for i := range rSec { - rKeys[z] = i.Value - rValues[i.Value] = rSec[i] + for pair := orderedmap.First(rSec); pair != nil; pair = pair.Next() { + rKeys[z] = pair.Key().Value + rValues[pair.Key().Value] = pair.Value() z++ }