Refactor Paths to OrderedMap.

This commit is contained in:
Shawn Poulson
2023-08-03 16:18:03 -04:00
parent f9dd682165
commit 652e818456
12 changed files with 216 additions and 100 deletions

View File

@@ -4,15 +4,15 @@
package v2
import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
v2low "github.com/pb33f/libopenapi/datamodel/low/v2"
"github.com/pb33f/libopenapi/orderedmap"
)
// Paths represents a high-level Swagger / OpenAPI Paths object, backed by a low-level one.
type Paths struct {
PathItems map[string]*PathItem
PathItems orderedmap.Map[string, *PathItem]
Extensions map[string]any
low *v2low.Paths
}
@@ -22,19 +22,19 @@ func NewPaths(paths *v2low.Paths) *Paths {
p := new(Paths)
p.low = paths
p.Extensions = high.ExtractExtensions(paths.Extensions)
pathItems := make(map[string]*PathItem)
pathItems := orderedmap.New[string, *PathItem]()
translateFunc := func(key low.KeyReference[string], value low.ValueReference[*v2low.PathItem]) (asyncResult[*PathItem], error) {
translateFunc := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v2low.PathItem]]) (asyncResult[*PathItem], error) {
return asyncResult[*PathItem]{
key: key.Value,
result: NewPathItem(value.Value),
key: pair.Key().Value,
result: NewPathItem(pair.Value().Value),
}, nil
}
resultFunc := func(result asyncResult[*PathItem]) error {
pathItems[result.key] = result.result
pathItems.Set(result.key, result.result)
return nil
}
_ = datamodel.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v2low.PathItem], asyncResult[*PathItem]](
_ = orderedmap.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v2low.PathItem], asyncResult[*PathItem]](
paths.PathItems, translateFunc, resultFunc,
)
p.PathItems = pathItems

View File

@@ -204,7 +204,7 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
highDoc := NewSwaggerDocument(doc)
assert.Len(t, highDoc.Paths.PathItems, 15)
upload := highDoc.Paths.PathItems["/pet/{petId}/uploadImage"]
upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage")
assert.Equal(t, "man", upload.Extensions["x-potato"])
assert.Nil(t, upload.Get)
assert.Nil(t, upload.Put)
@@ -262,7 +262,7 @@ func TestNewSwaggerDocument_Responses(t *testing.T) {
initTest()
highDoc := NewSwaggerDocument(doc)
upload := highDoc.Paths.PathItems["/pet/{petId}/uploadImage"].Post
upload := highDoc.Paths.PathItems.GetOrZero("/pet/{petId}/uploadImage").Post
assert.Len(t, upload.Responses.Codes, 1)

View File

@@ -14,6 +14,7 @@ import (
v2 "github.com/pb33f/libopenapi/datamodel/high/v2"
lowv2 "github.com/pb33f/libopenapi/datamodel/low/v2"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/stretchr/testify/assert"
)
@@ -330,7 +331,7 @@ func TestNewDocument_Paths(t *testing.T) {
}
func testBurgerShop(t *testing.T, h *Document, checkLines bool) {
burgersOp := h.Paths.PathItems["/burgers"]
burgersOp := h.Paths.PathItems.GetOrZero("/burgers")
assert.Len(t, burgersOp.GetOperations(), 1)
assert.Equal(t, "meaty", burgersOp.Extensions["x-burger-meta"])
@@ -410,7 +411,7 @@ func TestAsanaAsDoc(t *testing.T) {
}
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 118, len(d.Paths.PathItems))
assert.Equal(t, 118, orderedmap.Len(d.Paths.PathItems))
}
func TestDigitalOceanAsDocFromSHA(t *testing.T) {
@@ -434,7 +435,7 @@ func TestDigitalOceanAsDocFromSHA(t *testing.T) {
}
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 183, len(d.Paths.PathItems))
assert.Equal(t, 183, orderedmap.Len(d.Paths.PathItems))
}
@@ -448,7 +449,7 @@ func TestPetstoreAsDoc(t *testing.T) {
}
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 13, len(d.Paths.PathItems))
assert.Equal(t, 13, orderedmap.Len(d.Paths.PathItems))
}
func TestCircularReferencesDoc(t *testing.T) {
@@ -532,7 +533,7 @@ func TestDocument_MarshalJSON(t *testing.T) {
lowDoc, _ = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
newDoc := NewDocument(lowDoc)
assert.Equal(t, len(newDoc.Paths.PathItems), len(highDoc.Paths.PathItems))
assert.Equal(t, orderedmap.Len(newDoc.Paths.PathItems), orderedmap.Len(highDoc.Paths.PathItems))
assert.Equal(t, len(newDoc.Components.Schemas), len(highDoc.Components.Schemas))
}

View File

@@ -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["/pet"].Put.RequestBody.Content["application/json"]
mt := d.Paths.PathItems.GetOrZero("/pet").Put.RequestBody.Content["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["/pet"].Put.RequestBody.Content["application/json"]
mt := d.Paths.PathItems.GetOrZero("/pet").Put.RequestBody.Content["application/json"]
// render out the media type
yml, _ := mt.Render()

View File

@@ -5,9 +5,11 @@ package v3
import (
"fmt"
"io/ioutil"
"github.com/pb33f/libopenapi/datamodel"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"io/ioutil"
"github.com/pb33f/libopenapi/orderedmap"
)
// An example of how to create a new high-level OpenAPI 3+ document from an OpenAPI specification.
@@ -36,6 +38,6 @@ func Example_createHighLevelOpenAPIDocument() {
// Print out some details
fmt.Printf("Petstore contains %d paths and %d component schemas",
len(doc.Paths.PathItems), len(doc.Components.Schemas))
orderedmap.Len(doc.Paths.PathItems), len(doc.Components.Schemas))
// Output: Petstore contains 13 paths and 8 component schemas
}

View File

@@ -6,10 +6,10 @@ package v3
import (
"sort"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high"
"github.com/pb33f/libopenapi/datamodel/low"
v3low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
@@ -21,8 +21,8 @@ import (
// constraints.
// - https://spec.openapis.org/oas/v3.1.0#paths-object
type Paths struct {
PathItems map[string]*PathItem `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"`
PathItems orderedmap.Map[string, *PathItem] `json:"-" yaml:"-"`
Extensions map[string]any `json:"-" yaml:"-"`
low *v3low.Paths
}
@@ -31,21 +31,21 @@ func NewPaths(paths *v3low.Paths) *Paths {
p := new(Paths)
p.low = paths
p.Extensions = high.ExtractExtensions(paths.Extensions)
items := make(map[string]*PathItem)
items := orderedmap.New[string, *PathItem]()
type pathItemResult struct {
key string
value *PathItem
}
translateFunc := func(key low.KeyReference[string], value low.ValueReference[*v3low.PathItem]) (pathItemResult, error) {
return pathItemResult{key: key.Value, value: NewPathItem(value.Value)}, nil
translateFunc := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v3low.PathItem]]) (pathItemResult, error) {
return pathItemResult{key: pair.Key().Value, value: NewPathItem(pair.Value().Value)}, nil
}
resultFunc := func(value pathItemResult) error {
items[value.key] = value.value
items.Set(value.key, value.value)
return nil
}
_ = datamodel.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v3low.PathItem], pathItemResult](
_ = orderedmap.TranslateMapParallel[low.KeyReference[string], low.ValueReference[*v3low.PathItem], pathItemResult](
paths.PathItems, translateFunc, resultFunc,
)
p.PathItems = items
@@ -84,7 +84,9 @@ func (p *Paths) MarshalYAML() (interface{}, error) {
}
var mapped []*pathItem
for k, pi := range p.PathItems {
action := func(pair orderedmap.Pair[string, *PathItem]) error {
k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
if p.low != nil {
lpi := p.low.FindPath(k)
@@ -93,7 +95,9 @@ 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()
@@ -138,7 +142,9 @@ func (p *Paths) MarshalYAMLInline() (interface{}, error) {
}
var mapped []*pathItem
for k, pi := range p.PathItems {
action := func(pair orderedmap.Pair[string, *PathItem]) error {
k := pair.Key()
pi := pair.Value()
ln := 9999 // default to a high value to weight new content to the bottom.
if p.low != nil {
lpi := p.low.FindPath(k)
@@ -147,7 +153,9 @@ 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

View File

@@ -48,7 +48,7 @@ func TestPaths_MarshalYAML(t *testing.T) {
// mutate
deprecated := true
high.PathItems["/beer"].Get.Deprecated = &deprecated
high.PathItems.GetOrZero("/beer").Get.Deprecated = &deprecated
yml = `/foo/bar/bizzle:
get:
@@ -100,7 +100,7 @@ func TestPaths_MarshalYAMLInline(t *testing.T) {
// mutate
deprecated := true
high.PathItems["/beer"].Get.Deprecated = &deprecated
high.PathItems.GetOrZero("/beer").Get.Deprecated = &deprecated
yml = `/foo/bar/bizzle:
get:

View File

@@ -6,6 +6,7 @@ package v2
import (
"crypto/sha256"
"fmt"
"io"
"sort"
"strings"
"sync"
@@ -13,13 +14,14 @@ import (
"github.com/pb33f/libopenapi/datamodel"
"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"
)
// Paths represents a low-level Swagger / OpenAPI Paths object.
type Paths struct {
PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem]
PathItems orderedmap.Map[low.KeyReference[string], low.ValueReference[*PathItem]]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
}
@@ -29,23 +31,30 @@ 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) *low.ValueReference[*PathItem] {
for k, j := range p.PathItems {
if k.Value == path {
return &j
func (p *Paths) FindPath(path string) (result *low.ValueReference[*PathItem]) {
action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error {
if pair.Key().Value == path {
result = pair.ValuePtr()
return io.EOF
}
return nil
}
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) (*low.KeyReference[string], *low.ValueReference[*PathItem]) {
for k, j := range p.PathItems {
if k.Value == path {
return &k, &j
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 {
if pair.Key().Value == path {
key = pair.KeyPtr()
value = pair.ValuePtr()
return io.EOF
}
return nil
}
return nil, nil
_ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action)
return key, value
}
// FindExtension will attempt to locate an extension value given a name.
@@ -68,7 +77,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
currentNode *yaml.Node
pathNode *yaml.Node
}
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
pathsMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*PathItem]]()
in := make(chan buildInput)
out := make(chan pathBuildResult)
done := make(chan struct{})
@@ -115,7 +124,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
if !ok {
break
}
pathsMap[result.key] = result.value
pathsMap.Set(result.key, result.value)
}
close(done)
wg.Done()
@@ -154,14 +163,19 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
// Hash will return a consistent SHA256 Hash of the PathItem object
func (p *Paths) Hash() [32]byte {
var f []string
l := make([]string, len(p.PathItems))
l := make([]string, orderedmap.Len(p.PathItems))
keys := make(map[string]low.ValueReference[*PathItem])
z := 0
for k := range p.PathItems {
keys[k.Value] = p.PathItems[k]
l[z] = k.Value
action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error {
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 {
f = append(f, low.GenerateHashString(keys[l[k]].Value))

View File

@@ -6,6 +6,7 @@ package v3
import (
"crypto/sha256"
"fmt"
"io"
"sort"
"strings"
"sync"
@@ -13,6 +14,7 @@ import (
"github.com/pb33f/libopenapi/datamodel"
"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"
)
@@ -24,29 +26,36 @@ import (
// constraints.
// - https://spec.openapis.org/oas/v3.1.0#paths-object
type Paths struct {
PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem]
PathItems orderedmap.Map[low.KeyReference[string], low.ValueReference[*PathItem]]
Extensions map[low.KeyReference[string]]low.ValueReference[any]
*low.Reference
}
// FindPath will attempt to locate a PathItem using the provided path string.
func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
for k, j := range p.PathItems {
if k.Value == path {
return &j
func (p *Paths) FindPath(path string) (result *low.ValueReference[*PathItem]) {
action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error {
if pair.Key().Value == path {
result = pair.ValuePtr()
return io.EOF
}
return nil
}
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) (*low.KeyReference[string], *low.ValueReference[*PathItem]) {
for k, j := range p.PathItems {
if k.Value == path {
return &k, &j
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 {
if pair.Key().Value == path {
key = pair.KeyPtr()
value = pair.ValuePtr()
return io.EOF
}
return nil
}
return nil, nil
_ = orderedmap.For[low.KeyReference[string], low.ValueReference[*PathItem]](p.PathItems, action)
return key, value
}
// FindExtension will attempt to locate an extension using the specified string.
@@ -75,7 +84,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
currentNode *yaml.Node
pathNode *yaml.Node
}
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
pathsMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*PathItem]]()
in := make(chan buildInput)
out := make(chan buildResult)
done := make(chan struct{})
@@ -122,7 +131,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
if !ok {
break
}
pathsMap[result.key] = result.value
pathsMap.Set(result.key, result.value)
}
close(done)
wg.Done()
@@ -185,14 +194,19 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
// Hash will return a consistent SHA256 Hash of the PathItem object
func (p *Paths) Hash() [32]byte {
var f []string
l := make([]string, len(p.PathItems))
l := make([]string, orderedmap.Len(p.PathItems))
keys := make(map[string]low.ValueReference[*PathItem])
z := 0
for k := range p.PathItems {
keys[k.Value] = p.PathItems[k]
l[z] = k.Value
action := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*PathItem]]) error {
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 {
f = append(f, fmt.Sprintf("%s-%s", l[k], low.GenerateHashString(keys[l[k]].Value)))

View File

@@ -5,12 +5,14 @@ package libopenapi
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel"
"net/url"
"os"
"strings"
"testing"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/datamodel/high"
v3high "github.com/pb33f/libopenapi/datamodel/high/v3"
low "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -47,7 +49,7 @@ func ExampleNewDocument_fromOpenAPI3Document() {
}
// get a count of the number of paths and schemas.
paths := len(v3Model.Model.Paths.PathItems)
paths := orderedmap.Len(v3Model.Model.Paths.PathItems)
schemas := len(v3Model.Model.Components.Schemas)
// print the number of paths and schemas in the document
@@ -153,7 +155,7 @@ func ExampleNewDocument_fromSwaggerDocument() {
}
// get a count of the number of paths and schemas.
paths := len(v2Model.Model.Paths.PathItems)
paths := orderedmap.Len(v2Model.Model.Paths.PathItems)
schemas := len(v2Model.Model.Definitions.Definitions)
// print the number of paths and schemas in the document
@@ -184,7 +186,7 @@ func ExampleNewDocument_fromUnknownVersion() {
errors = errs
}
if len(errors) <= 0 {
paths = len(v3Model.Model.Paths.PathItems)
paths = orderedmap.Len(v3Model.Model.Paths.PathItems)
schemas = len(v3Model.Model.Components.Schemas)
}
}
@@ -194,7 +196,7 @@ func ExampleNewDocument_fromUnknownVersion() {
errors = errs
}
if len(errors) <= 0 {
paths = len(v2Model.Model.Paths.PathItems)
paths = orderedmap.Len(v2Model.Model.Paths.PathItems)
schemas = len(v2Model.Model.Definitions.Definitions)
}
}
@@ -641,10 +643,10 @@ func ExampleNewDocument_modifyAndReRender() {
}
// capture original number of paths
originalPaths := len(v3Model.Model.Paths.PathItems)
originalPaths := orderedmap.Len(v3Model.Model.Paths.PathItems)
// add the path to the document
v3Model.Model.Paths.PathItems["/new/path"] = newPath
v3Model.Model.Paths.PathItems.Set("/new/path", newPath)
// render the document back to bytes and reload the model.
rawBytes, _, newModel, errs := doc.RenderAndReload()
@@ -655,7 +657,7 @@ func ExampleNewDocument_modifyAndReRender() {
}
// capture new number of paths after re-rendering
newPaths := len(newModel.Model.Paths.PathItems)
newPaths := orderedmap.Len(newModel.Model.Paths.PathItems)
// print the number of paths and schemas in the document
fmt.Printf("There were %d original paths. There are now %d paths in the document\n", originalPaths, newPaths)

View File

@@ -4,13 +4,22 @@ package libopenapi
import (
"fmt"
"os"
"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"
"os"
"strings"
"testing"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/high/base"
v3high "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/what-changed/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoadDocument_Simple_V2(t *testing.T) {
@@ -244,12 +253,12 @@ func TestDocument_RenderAndReload(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["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,
h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security = append(h.Paths.PathItems.GetOrZero("/pet/{petId}").Delete.Security,
&base.SecurityRequirement{Requirements: map[string][]string{
"pizza-and-cake": {"read:abook", "write:asong"},
}})
@@ -262,13 +271,13 @@ func TestDocument_RenderAndReload(t *testing.T) {
assert.NotNil(t, bytes)
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["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.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, "I am a teapot, filled with love.",
h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example)
@@ -479,7 +488,7 @@ paths:
}
// extract operation.
operation := result.Model.Paths.PathItems["/something"].Get
operation := result.Model.Paths.PathItems.GetOrZero("/something").Get
// print it out.
fmt.Printf("param1: %s, is reference? %t, original reference %s",
@@ -647,7 +656,7 @@ paths:
// panic(errs)
// }
//
// assert.Equal(t, "crs", result.Model.Paths.PathItems["/test"].Get.Parameters[0].Name)
// assert.Equal(t, "crs", result.Model.Paths.PathItems.GetOrZero("/test").Get.Parameters[0].Name)
//}
func TestDocument_ExampleMap(t *testing.T) {
@@ -858,3 +867,59 @@ components:
assert.Len(t, m.Index.GetCircularReferences(), 0)
}
// Ensure document ordering is preserved after building and loading.
func TestDocument_Render_PreserveOrder(t *testing.T) {
t.Run("Paths", func(t *testing.T) {
const pathCount = 100
doc, err := NewDocument([]byte(`openapi: 3.1.0`))
require.NoError(t, err)
model, errs := doc.BuildV3Model()
require.Empty(t, errs)
pathItems := orderedmap.New[string, *v3high.PathItem]()
model.Model.Paths = &v3high.Paths{
PathItems: pathItems,
}
for i := 0; i < pathCount; i++ {
pathItem := &v3high.PathItem{
Get: &v3high.Operation{
Parameters: make([]*v3high.Parameter, 0),
},
}
pathName := fmt.Sprintf("/foobar/%d", i)
pathItems.Set(pathName, pathItem)
}
checkOrder := func(t *testing.T, doc Document) {
model, errs := doc.BuildV3Model()
require.Empty(t, errs)
pathItems := model.Model.Paths.PathItems
require.Equal(t, pathCount, orderedmap.Len(pathItems))
var i int
_ = orderedmap.For(model.Model.Paths.PathItems, func(pair orderedmap.Pair[string, *v3high.PathItem]) error {
pathName := fmt.Sprintf("/foobar/%d", i)
assert.Equal(t, pathName, pair.Key())
i++
return nil
})
assert.Equal(t, pathCount, i)
}
checkOrder(t, doc)
yamlBytes, doc, _, errs := doc.RenderAndReload()
require.Empty(t, errs)
t.Run("Unmarshalled YAML ordering", func(t *testing.T) {
// Reload YAML into new Document, verify ordering.
doc2, err := NewDocument(yamlBytes)
require.NoError(t, err)
checkOrder(t, doc2)
})
t.Run("Reloaded document ordering", func(t *testing.T) {
// Verify ordering of reloaded document after call to RenderAndReload().
checkOrder(t, doc)
})
})
}

View File

@@ -4,11 +4,13 @@
package model
import (
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/v2"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"reflect"
"sync"
"github.com/pb33f/libopenapi/datamodel/low"
v2 "github.com/pb33f/libopenapi/datamodel/low/v2"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/orderedmap"
)
// PathsChanges represents changes found between two Swagger or OpenAPI Paths Objects.
@@ -75,12 +77,16 @@ func ComparePaths(l, r any) *PathsChanges {
lKeys := make(map[string]low.ValueReference[*v2.PathItem])
rKeys := make(map[string]low.ValueReference[*v2.PathItem])
for k := range lPath.PathItems {
lKeys[k.Value] = lPath.PathItems[k]
laction := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v2.PathItem]]) error {
lKeys[pair.Key().Value] = pair.Value()
return nil
}
for k := range rPath.PathItems {
rKeys[k.Value] = rPath.PathItems[k]
_ = 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 {
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
@@ -146,12 +152,16 @@ func ComparePaths(l, r any) *PathsChanges {
lKeys := make(map[string]low.ValueReference[*v3.PathItem])
rKeys := make(map[string]low.ValueReference[*v3.PathItem])
for k := range lPath.PathItems {
lKeys[k.Value] = lPath.PathItems[k]
laction := func(pair orderedmap.Pair[low.KeyReference[string], low.ValueReference[*v3.PathItem]]) error {
lKeys[pair.Key().Value] = pair.Value()
return nil
}
for k := range rPath.PathItems {
rKeys[k.Value] = rPath.PathItems[k]
_ = 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 {
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