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

openapi3: optimize Unmarshal for maplike structs #882

Merged
merged 3 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/docs/openapi3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func NewCallback(opts ...NewCallbackOption) *Callback
NewCallback builds a Callback object with path items in insertion order.

func NewCallbackWithCapacity(cap int) *Callback
NewCallbackWithCapacity builds a Callback object of the given capacity.
NewCallbackWithCapacity builds a callback object of the given capacity.

func (callback Callback) JSONLookup(token string) (interface{}, error)
JSONLookup implements
Expand All @@ -157,10 +157,10 @@ func (callback Callback) JSONLookup(token string) (interface{}, error)
func (callback *Callback) Len() int
Len returns the amount of keys in callback excluding callback.Extensions.

func (callback *Callback) Map() map[string]*PathItem
func (callback *Callback) Map() (m map[string]*PathItem)
Map returns callback as a 'map'. Note: iteration on Go maps is not ordered.

func (callback Callback) MarshalJSON() ([]byte, error)
func (callback *Callback) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Callback.

func (callback *Callback) Set(key string, value *PathItem)
Expand Down Expand Up @@ -944,10 +944,10 @@ func (paths Paths) JSONLookup(token string) (interface{}, error)
func (paths *Paths) Len() int
Len returns the amount of keys in paths excluding paths.Extensions.

func (paths *Paths) Map() map[string]*PathItem
func (paths *Paths) Map() (m map[string]*PathItem)
Map returns paths as a 'map'. Note: iteration on Go maps is not ordered.

func (paths Paths) MarshalJSON() ([]byte, error)
func (paths *Paths) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Paths.

func (paths *Paths) Set(key string, value *PathItem)
Expand Down Expand Up @@ -1152,10 +1152,10 @@ func (responses Responses) JSONLookup(token string) (interface{}, error)
func (responses *Responses) Len() int
Len returns the amount of keys in responses excluding responses.Extensions.

func (responses *Responses) Map() map[string]*ResponseRef
func (responses *Responses) Map() (m map[string]*ResponseRef)
Map returns responses as a 'map'. Note: iteration on Go maps is not ordered.

func (responses Responses) MarshalJSON() ([]byte, error)
func (responses *Responses) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Responses.

func (responses *Responses) Set(key string, value *ResponseRef)
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,6 @@ jobs:
run: |
[[ "$(git grep -F yaml. -- openapi3/ | grep -v _test.go | wc -l)" = 1 ]]

- if: runner.os == 'Linux'
name: Ensure non-pointer MarshalJSON
run: |
! git grep -InE 'func[^{}]+[*][^{}]+[)].MarshalJSON[(][)]'

- if: runner.os == 'Linux'
name: Use `loader := NewLoader(); loader.Load ...`
run: |
Expand Down
97 changes: 78 additions & 19 deletions maps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ names+=('paths')
[[ "${#types[@]}" = "${#names[@]}" ]]
[[ "${#types[@]}" = "$(git grep -InF ' m map[string]*' -- openapi3/loader.go | wc -l)" ]]

cat <<EOF >"$maplike"

maplike_header() {
cat <<EOF >"$maplike"
package openapi3

import (
Expand All @@ -39,10 +41,13 @@ import (

"github.com/go-openapi/jsonpointer"
)

EOF
}


cat <<EOF >"$maplike_test"
test_header() {
cat <<EOF >"$maplike_test"
package openapi3

import (
Expand All @@ -53,16 +58,32 @@ import (

func TestMaplikeMethods(t *testing.T) {
t.Parallel()

EOF
}

for i in "${!types[@]}"; do
type=${types[$i]}
value_type=${value_types[$i]}
deref_v=${deref_vs[$i]}
name=${names[$i]}

test_footer() {
echo "}" >>"$maplike_test"
}


maplike_NewWithCapa() {
cat <<EOF >>"$maplike"
// 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#'*'}{m: make(map[string]${value_type}, cap)}
}

EOF
}


maplike_ValueSetLen() {
cat <<EOF >>"$maplike"
// Value returns the ${name} for key or nil
func (${name} ${type}) Value(key string) ${value_type} {
if ${name}.Len() == 0 {
Expand All @@ -82,21 +103,31 @@ func (${name} ${type}) Set(key string, value ${value_type}) {

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

// Map returns ${name} as a 'map'.
// Note: iteration on Go maps is not ordered.
func (${name} ${type}) Map() map[string]${value_type} {
if ${name}.Len() == 0 {
return nil
func (${name} ${type}) Map() (m map[string]${value_type}) {
if ${name} == nil || len(${name}.m) == 0 {
return make(map[string]${value_type})
}
m = make(map[string]${value_type}, len(${name}.m))
for k, v := range ${name}.m {
m[k] = v
}
return ${name}.m
return
}

EOF
}


maplike_Pointable() {
cat <<EOF >>"$maplike"
var _ jsonpointer.JSONPointable = (${type})(nil)

// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
Expand All @@ -112,8 +143,14 @@ func (${name} ${type#'*'}) JSONLookup(token string) (interface{}, error) {
}
}

EOF
}


maplike_UnMarsh() {
cat <<EOF >>"$maplike"
// MarshalJSON returns the JSON encoding of ${type#'*'}.
func (${name} ${type#'*'}) MarshalJSON() ([]byte, error) {
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
Expand Down Expand Up @@ -163,33 +200,55 @@ func (${name} ${type}) UnmarshalJSON(data []byte) (err error) {
return
}
EOF
}

cat <<EOF >>"$maplike_test"

test_body() {
cat <<EOF >>"$maplike_test"
t.Run("${type}", func(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
x := (${type})(nil)
require.Equal(t, 0, x.Len())
require.Equal(t, (map[string]${value_type})(nil), x.Map())
require.Equal(t, map[string]${value_type}{}, x.Map())
require.Equal(t, (${value_type})(nil), x.Value("key"))
require.Panics(t, func() { x.Set("key", &${value_type#'*'}{}) })
})
t.Run("nonnil", func(t *testing.T) {
x := &${type#'*'}{}
require.Equal(t, 0, x.Len())
require.Equal(t, (map[string]${value_type})(nil), x.Map())
require.Equal(t, map[string]${value_type}{}, x.Map())
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"))
})
})

EOF
}



maplike_header
test_header

for i in "${!types[@]}"; do
type=${types[$i]}
value_type=${value_types[$i]}
deref_v=${deref_vs[$i]}
name=${names[$i]}

type="$type" name="$name" value_type="$value_type" maplike_NewWithCapa
type="$type" name="$name" value_type="$value_type" maplike_ValueSetLen
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"

type="$type" value_type="$value_type" test_body


done

cat <<EOF >>"$maplike_test"
}
EOF
test_footer
5 changes: 0 additions & 5 deletions openapi3/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ type Callback struct {
m map[string]*PathItem
}

// NewCallbackWithCapacity builds a Callback object of the given capacity.
func NewCallbackWithCapacity(cap int) *Callback {
return &Callback{m: make(map[string]*PathItem, cap)}
}

// NewCallback builds a Callback object with path items in insertion order.
func NewCallback(opts ...NewCallbackOption) *Callback {
Callback := NewCallbackWithCapacity(len(opts))
Expand Down
1 change: 1 addition & 0 deletions openapi3/internalize_refs.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri
}
doc.derefExamples(components.Examples, refNameResolver, false)
doc.derefLinks(components.Links, refNameResolver, false)

for _, cb := range components.Callbacks {
isExternal := doc.addCallbackToSpec(cb, refNameResolver, false)
if cb != nil && cb.Value != nil {
Expand Down