Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ordered maps #695

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ func NewCallback(opts ...NewCallbackOption) *Callback
func NewCallbackWithCapacity(cap int) *Callback
NewCallbackWithCapacity builds a callback object of the given capacity.

func (callback *Callback) Iter() *callbackKV
Iter returns a pointer to the first pair, in insertion order.

func (callback Callback) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://github.com/go-openapi/jsonpointer#JSONPointable
Expand Down Expand Up @@ -937,6 +940,9 @@ func (paths *Paths) InMatchingOrder() []string
When matching URLs, concrete (non-templated) paths would be matched before
their templated counterparts.

func (paths *Paths) Iter() *pathsKV
Iter returns a pointer to the first pair, in insertion order.

func (paths Paths) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://github.com/go-openapi/jsonpointer#JSONPointable
Expand Down Expand Up @@ -1145,6 +1151,9 @@ func NewResponsesWithCapacity(cap int) *Responses
func (responses *Responses) Default() *ResponseRef
Default returns the default response

func (responses *Responses) Iter() *responsesKV
Iter returns a pointer to the first pair, in insertion order.

func (responses Responses) JSONLookup(token string) (interface{}, error)
JSONLookup implements
https://github.com/go-openapi/jsonpointer#JSONPointable
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ jobs:
run: |
[[ 31 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]]

- if: runner.os == 'Linux'
name: Ensure opaque usage of orderedmap package
run: |
! git grep -InE orderedmap -- .github/docs/

- if: runner.os == 'Linux'
name: Missing validation of unknown fields in extensions
run: |
Expand Down
50 changes: 50 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
0 0s kin-openapi.git issue645-bis-bis λ ge '^\s+[A-Z]\w+ +map' openapi3/ |\grep -vF 'Extensions map['
openapi3/components.go:13: Callbacks map[string]*CallbackRef
openapi3/components.go:14: Examples map[string]*ExampleRef
openapi3/components.go:15: Headers map[string]*HeaderRef
openapi3/components.go:16: Links map[string]*LinkRef
openapi3/components.go:17: ParametersMap map[string]*ParameterRef
openapi3/components.go:18: RequestBodies map[string]*RequestBodyRef
openapi3/components.go:19: ResponseBodies map[string]*ResponseRef
openapi3/components.go:20: Schemas map[string]*SchemaRef
openapi3/components.go:21: SecuritySchemes map[string]*SecuritySchemeRef
openapi3/discriminator.go:14: Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
openapi3/link.go:18: Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"`
openapi3/loader_read_from_uri_func_test.go:36: Sources map[string][]byte
openapi3/media_type.go:21: Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
openapi3/security_scheme.go:305: Scopes map[string]string `json:"scopes" yaml:"scopes"` // required
openapi3/server.go:58: Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`


0 3s kin-openapi.git issue645 λ ge -F '.Map()'
maps.sh:226: require.Equal(t, map[string]${value_type}{}, x.Map())
maps.sh:233: require.Equal(t, map[string]${value_type}{}, x.Map())
maps.sh:237: m := x.Map()
openapi2conv/openapi2_conv.go:623: for path, pathItem := range doc3.Paths.Map() {
openapi2conv/openapi2_conv.go:986: resultResponses, err := FromV3Responses(responses.Map(), doc3.Components)
openapi3/callback.go:44: for key := range callback.Map() {
openapi3/internalize_refs.go:326: doc.derefResponseBodies(rs.Map(), refNameResolver, parentIsExternal)
openapi3/internalize_refs.go:365: cbValue := (*cb.Value).Map()
openapi3/internalize_refs.go:436: cbValue := (*cb.Value).Map()
openapi3/internalize_refs.go:442: doc.derefPaths(doc.Paths.Map(), refNameResolver, false)
openapi3/loader.go:241: for _, pathItem := range doc.Paths.Map() {
openapi3/loader.go:335: cursor = c.Map() // om map[string]*ResponseRef
openapi3/loader.go:337: cursor = c.Map() // om map[string]*PathItem
openapi3/loader.go:339: cursor = c.Map() // om map[string]*PathItem
openapi3/loader.go:1018: for _, pathItem := range value.Map() {
openapi3/loader.go:1111: for _, response := range operation.Responses.Map() {
openapi3/maplike_test.go:17: require.Equal(t, map[string]*ResponseRef{}, x.Map())
openapi3/maplike_test.go:24: require.Equal(t, map[string]*ResponseRef{}, x.Map())
openapi3/maplike_test.go:28: m := x.Map()
openapi3/maplike_test.go:40: require.Equal(t, map[string]*PathItem{}, x.Map())
openapi3/maplike_test.go:47: require.Equal(t, map[string]*PathItem{}, x.Map())
openapi3/maplike_test.go:51: m := x.Map()
openapi3/maplike_test.go:63: require.Equal(t, map[string]*PathItem{}, x.Map())
openapi3/maplike_test.go:70: require.Equal(t, map[string]*PathItem{}, x.Map())
openapi3/maplike_test.go:74: m := x.Map()
openapi3/paths.go:48: for key := range paths.Map() {
openapi3/paths.go:152: for path := range paths.Map() {
openapi3/paths.go:190: for path, pathItem := range paths.Map() {
openapi3/paths.go:201: for urlPath, pathItem := range paths.Map() {
openapi3/response.go:89: for key := range responses.Map() {
routers/legacy/router.go:67: for path, pathItem := range doc.Paths.Map() {
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/perimeterx/marshmallow v1.1.5
github.com/stretchr/testify v1.8.4
github.com/wk8/go-ordered-map/v2 v2.1.8
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/swag v0.22.8 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
Expand Down Expand Up @@ -26,6 +30,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
92 changes: 54 additions & 38 deletions maps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ names+=('paths')
[[ "${#types[@]}" = "${#value_types[@]}" ]]
[[ "${#types[@]}" = "${#deref_vs[@]}" ]]
[[ "${#types[@]}" = "${#names[@]}" ]]
[[ "${#types[@]}" = "$(git grep -InF ' m map[string]*' -- openapi3/loader.go | wc -l)" ]]
[[ "${#types[@]}" = "$(git grep -InF ' om map[string]*' -- openapi3/loader.go | wc -l)" ]] #FIXME: !map


maplike_header() {
Expand All @@ -36,10 +36,10 @@ package openapi3

import (
"encoding/json"
"sort"
"strings"

"github.com/go-openapi/jsonpointer"
orderedmap "github.com/wk8/go-ordered-map/v2"
)

EOF
Expand Down Expand Up @@ -73,9 +73,9 @@ maplike_NewWithCapa() {
// New${type#'*'}WithCapacity builds a ${name} object of the given capacity.
func New${type#'*'}WithCapacity(cap int) ${type} {
if cap == 0 {
return &${type#'*'}{m: make(map[string]${value_type})}
return &${type#'*'}{om: orderedmap.New[string, ${value_type}]()}
}
return &${type#'*'}{m: make(map[string]${value_type}, cap)}
return &${type#'*'}{om: orderedmap.New[string, ${value_type}](cap)}
}

EOF
Expand All @@ -89,35 +89,35 @@ func (${name} ${type}) Value(key string) ${value_type} {
if ${name}.Len() == 0 {
return nil
}
return ${name}.m[key]
return ${name}.om.Value(key)
}

// Set adds or replaces key 'key' of '${name}' with 'value'.
// Note: '${name}' MUST be non-nil
func (${name} ${type}) Set(key string, value ${value_type}) {
if ${name}.m == nil {
${name}.m = make(map[string]${value_type})
if ${name}.om == nil {
${name}.om = New${type#'*'}WithCapacity(0).om
}
${name}.m[key] = value
_, _ = ${name}.om.Set(key, value)
}

// Len returns the amount of keys in ${name} excluding ${name}.Extensions.
func (${name} ${type}) Len() int {
if ${name} == nil || ${name}.m == nil {
if ${name} == nil || ${name}.om == nil {
return 0
}
return len(${name}.m)
return ${name}.om.Len()
}

// Map returns ${name} as a 'map'.
// Note: iteration on Go maps is not ordered.
func (${name} ${type}) Map() (m map[string]${value_type}) {
if ${name} == nil || len(${name}.m) == 0 {
if ${name} == nil || ${name}.om == nil {
return make(map[string]${value_type})
}
m = make(map[string]${value_type}, len(${name}.m))
for k, v := range ${name}.m {
m[k] = v
m = make(map[string]${value_type}, ${name}.Len())
for pair := ${name}.Iter(); pair != nil; pair = pair.Next() {
m[pair.Key] = pair.Value
}
return
}
Expand All @@ -126,6 +126,27 @@ EOF
}


maplike_IterNext() {
cat <<EOF >>"$maplike"
type ${name}KV orderedmap.Pair[string, ${value_type}] //FIXME: pub?
// Iter returns a pointer to the first pair, in insertion order.
func (${name} ${type}) Iter() *${name}KV {
if ${name}.Len() == 0 {
return nil
}
return (*${name}KV)(${name}.om.Oldest())
}

// Next returns a pointer to the next pair, in insertion order.
func (pair *${name}KV) Next() *${name}KV {
ompair := (*orderedmap.Pair[string, ${value_type}])(pair)
return (*${name}KV)(ompair.Next())
}

EOF
}


maplike_Pointable() {
cat <<EOF >>"$maplike"
var _ jsonpointer.JSONPointable = (${type})(nil)
Expand All @@ -151,36 +172,28 @@ maplike_UnMarsh() {
cat <<EOF >>"$maplike"
// MarshalJSON returns the JSON encoding of ${type#'*'}.
func (${name} ${type}) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, ${name}.Len()+len(${name}.Extensions))
for k, v := range ${name}.Extensions {
m[k] = v
om := orderedmap.New[string, interface{}](${name}.Len() + len(${name}.Extensions))
for pair := ${name}.Iter(); pair != nil; pair = pair.Next() {
om.Set(pair.Key, pair.Value)
}
for k, v := range ${name}.Map() {
m[k] = v
for k, v := range ${name}.Extensions {
om.Set(k, v)
}
return json.Marshal(m)
return om.MarshalJSON()
}

// UnmarshalJSON sets ${type#'*'} to a copy of data.
func (${name} ${type}) UnmarshalJSON(data []byte) (err error) {
var m map[string]interface{}
if err = json.Unmarshal(data, &m); err != nil {
om := orderedmap.New[string, interface{}]()
if err = json.Unmarshal(data, &om); err != nil {
return
}

ks := make([]string, 0, len(m))
for k := range m {
ks = append(ks, k)
}
sort.Strings(ks)

x := ${type#'*'}{
Extensions: make(map[string]interface{}),
m: make(map[string]${value_type}, len(m)),
}
x := New${type#'*'}WithCapacity(om.Len())
x.Extensions = make(map[string]interface{})

for _, k := range ks {
v := m[k]
for pair := om.Oldest(); pair != nil; pair = pair.Next() {
k, v := pair.Key, pair.Value
if strings.HasPrefix(k, "x-") {
x.Extensions[k] = v
continue
Expand All @@ -194,9 +207,9 @@ func (${name} ${type}) UnmarshalJSON(data []byte) (err error) {
if err = vv.UnmarshalJSON(data); err != nil {
return
}
x.m[k] = &vv
x.Set(k, &vv)
}
*${name} = x
*${name} = *x
return
}
EOF
Expand All @@ -221,8 +234,10 @@ test_body() {
require.Equal(t, (${value_type})(nil), x.Value("key"))
x.Set("key", &${value_type#'*'}{})
require.Equal(t, 1, x.Len())
require.Equal(t, map[string]${value_type}{"key": {}}, x.Map())
require.Equal(t, &${value_type#'*'}{}, x.Value("key"))
m := x.Map()
require.Equal(t, map[string]${value_type}{"key": {}}, m)
m["key"].Ref = "bla"
require.Equal(t, &${value_type#'*'}{Ref: "bla"}, x.Value("key"))
})
})

Expand All @@ -242,6 +257,7 @@ for i in "${!types[@]}"; do

type="$type" name="$name" value_type="$value_type" maplike_NewWithCapa
type="$type" name="$name" value_type="$value_type" maplike_ValueSetLen
type="$type" name="$name" value_type="$value_type" maplike_IterNext
type="$type" name="$name" deref_v="$deref_v" maplike_Pointable
type="$type" name="$name" value_type="$value_type" maplike_UnMarsh
[[ $((i+1)) != "${#types[@]}" ]] && echo >>"$maplike"
Expand Down
4 changes: 3 additions & 1 deletion openapi3/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package openapi3
import (
"context"
"sort"

orderedmap "github.com/wk8/go-ordered-map/v2"
)

// Callback is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
type Callback struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`

m map[string]*PathItem
om *orderedmap.OrderedMap[string, *PathItem]
}

// NewCallback builds a Callback object with path items in insertion order.
Expand Down
Loading
Loading