Skip to content

Commit

Permalink
openapi3: optimize Unmarshal for maplike structs (getkin#882)
Browse files Browse the repository at this point in the history
  • Loading branch information
fenollp authored Dec 2, 2023
1 parent 0bd3056 commit 36afc12
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 80 deletions.
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
Loading

0 comments on commit 36afc12

Please sign in to comment.