Skip to content

Commit

Permalink
Infer default values from prepared schema (#195)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop committed Mar 6, 2024
1 parent af28bea commit a631dc6
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 13 deletions.
4 changes: 2 additions & 2 deletions _examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ require (
github.com/rs/cors v1.10.1
github.com/stretchr/testify v1.9.0
github.com/swaggest/assertjson v1.9.0
github.com/swaggest/jsonschema-go v0.3.66
github.com/swaggest/openapi-go v0.2.47
github.com/swaggest/jsonschema-go v0.3.69
github.com/swaggest/openapi-go v0.2.49
github.com/swaggest/rest v0.0.0-00010101000000-000000000000
github.com/swaggest/swgui v1.8.0
github.com/swaggest/usecase v1.3.1
Expand Down
8 changes: 4 additions & 4 deletions _examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7
github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
github.com/swaggest/form/v5 v5.1.1 h1:ct6/rOQBGrqWUQ0FUv3vW5sHvTUb31AwTUWj947N6cY=
github.com/swaggest/form/v5 v5.1.1/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
github.com/swaggest/jsonschema-go v0.3.66 h1:4c5d7NRRqPLTswsbaypKqcMe3Z+CYHE3/lGsPIByp8o=
github.com/swaggest/jsonschema-go v0.3.66/go.mod h1:7N43/CwdaWgPUDfYV70K7Qm79tRqe/al7gLSt9YeGIE=
github.com/swaggest/openapi-go v0.2.47 h1:qBh28FHz0M1QSJmGRCcY/Xt9WKRkECKXGUbw/U8IcJ4=
github.com/swaggest/openapi-go v0.2.47/go.mod h1:MK5O26lG289kFgMOyXK1VXDoTZ89KJ8Vt0v0ic23zZw=
github.com/swaggest/jsonschema-go v0.3.69 h1:BNEajhoQjnEQzxZqPmjD1Pcs1pxnjx/X9L5KWllLHxo=
github.com/swaggest/jsonschema-go v0.3.69/go.mod h1:7N43/CwdaWgPUDfYV70K7Qm79tRqe/al7gLSt9YeGIE=
github.com/swaggest/openapi-go v0.2.49 h1:WxAfdde6DlfQPQayHWTb64CywK9+vpToi7iN17iDPO8=
github.com/swaggest/openapi-go v0.2.49/go.mod h1:MK5O26lG289kFgMOyXK1VXDoTZ89KJ8Vt0v0ic23zZw=
github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I=
github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg=
github.com/swaggest/swgui v1.8.0 h1:dPu8TsYIOraaObAkyNdoiLI8mu7nOqQ6SU7HOv254rM=
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ require (
github.com/stretchr/testify v1.8.2
github.com/swaggest/assertjson v1.9.0
github.com/swaggest/form/v5 v5.1.1
github.com/swaggest/jsonschema-go v0.3.66
github.com/swaggest/openapi-go v0.2.47
github.com/swaggest/jsonschema-go v0.3.69
github.com/swaggest/openapi-go v0.2.49
github.com/swaggest/refl v1.3.0
github.com/swaggest/usecase v1.3.1
)
Expand Down
7 changes: 4 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7
github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
github.com/swaggest/form/v5 v5.1.1 h1:ct6/rOQBGrqWUQ0FUv3vW5sHvTUb31AwTUWj947N6cY=
github.com/swaggest/form/v5 v5.1.1/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
github.com/swaggest/jsonschema-go v0.3.66 h1:4c5d7NRRqPLTswsbaypKqcMe3Z+CYHE3/lGsPIByp8o=
github.com/swaggest/jsonschema-go v0.3.66/go.mod h1:7N43/CwdaWgPUDfYV70K7Qm79tRqe/al7gLSt9YeGIE=
github.com/swaggest/openapi-go v0.2.47 h1:qBh28FHz0M1QSJmGRCcY/Xt9WKRkECKXGUbw/U8IcJ4=
github.com/swaggest/openapi-go v0.2.47/go.mod h1:MK5O26lG289kFgMOyXK1VXDoTZ89KJ8Vt0v0ic23zZw=
github.com/swaggest/jsonschema-go v0.3.69 h1:BNEajhoQjnEQzxZqPmjD1Pcs1pxnjx/X9L5KWllLHxo=
github.com/swaggest/jsonschema-go v0.3.69/go.mod h1:7N43/CwdaWgPUDfYV70K7Qm79tRqe/al7gLSt9YeGIE=
github.com/swaggest/openapi-go v0.2.49 h1:WxAfdde6DlfQPQayHWTb64CywK9+vpToi7iN17iDPO8=
github.com/swaggest/openapi-go v0.2.49/go.mod h1:MK5O26lG289kFgMOyXK1VXDoTZ89KJ8Vt0v0ic23zZw=
github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I=
github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg=
github.com/swaggest/usecase v1.3.1 h1:JdKV30MTSsDxAXxkldLNcEn8O2uf565khyo6gr5sS+w=
Expand Down
24 changes: 22 additions & 2 deletions request/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/swaggest/form/v5"
"github.com/swaggest/jsonschema-go"
"github.com/swaggest/openapi-go"
"github.com/swaggest/refl"
"github.com/swaggest/rest"
Expand Down Expand Up @@ -38,6 +39,9 @@ type DecoderFactory struct {
// If not set encoding/json.Decoder is used.
JSONReader func(rd io.Reader, v interface{}) error

// JSONSchemaReflector is optional, it is called to infer "default" values.
JSONSchemaReflector *jsonschema.Reflector

formDecoders map[rest.ParamIn]*form.Decoder
decoderFunctions map[rest.ParamIn]decoderFunc
defaultValDecoder *form.Decoder
Expand Down Expand Up @@ -257,7 +261,7 @@ func (df *DecoderFactory) jsonParams(formDecoder *form.Decoder, in rest.ParamIn,
func (df *DecoderFactory) makeDefaultDecoder(input interface{}, m *decoder) {
defaults := url.Values{}

refl.WalkFieldsRecursively(reflect.ValueOf(input), func(_ reflect.Value, sf reflect.StructField, path []reflect.StructField) {
refl.WalkFieldsRecursively(reflect.ValueOf(input), func(v reflect.Value, sf reflect.StructField, path []reflect.StructField) {
var key string

for _, p := range path {
Expand All @@ -278,8 +282,24 @@ func (df *DecoderFactory) makeDefaultDecoder(input interface{}, m *decoder) {
key += "[" + sf.Name + "]"
}

if d, ok := sf.Tag.Lookup(defaultTag); ok {
if d, ok := sf.Tag.Lookup(defaultTag); ok { //nolint:nestif
defaults[key] = []string{d}
} else if df.JSONSchemaReflector != nil {
vi := v.Interface()

s, err := df.JSONSchemaReflector.Reflect(vi)
if err != nil {
panic(err.Error())
}

if s.Default != nil {
d, err := json.Marshal(s.Default)
if err != nil {
panic(err.Error())
}

defaults[key] = []string{strings.Trim(string(d), `"`)}
}
}
})

Expand Down
67 changes: 67 additions & 0 deletions request/factory_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package request_test

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -10,6 +11,7 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/swaggest/jsonschema-go"
"github.com/swaggest/rest"
"github.com/swaggest/rest/request"
)
Expand Down Expand Up @@ -269,3 +271,68 @@ func TestDecoderFactory_MakeDecoder_header_case_sensitivity(t *testing.T) {
assert.Equal(t, "hello!", v.C)
assert.Equal(t, "hello!", v.D)
}

type defaultFromSchema string

func (d *defaultFromSchema) PrepareJSONSchema(schema *jsonschema.Schema) error {
schema.WithDefault(enum1)
schema.WithTitle("Value with default from schema")

return nil
}

type defaultFromSchemaVal string

func (d defaultFromSchemaVal) PrepareJSONSchema(schema *jsonschema.Schema) error {
schema.WithDefault(enum1)
schema.WithTitle("Value with default from schema")

return nil
}

const (
enum1 = "all"
enum2 = "none"
)

func (d *defaultFromSchema) Enum() []interface{} {
return []interface{}{enum1, enum2}
}

func (d defaultFromSchemaVal) Enum() []interface{} {
return []interface{}{enum1, enum2}
}

func TestNewDecoderFactory_default(t *testing.T) {
type NewThing struct {
DefaultedQuery *defaultFromSchema `query:"dq"`
DefaultedPtr *defaultFromSchema `json:"dp,omitempty"`
Defaulted defaultFromSchema `json:"d"`
DefaultedTag defaultFromSchema `query:"dt" default:"none"`
DefaultedQueryVal *defaultFromSchemaVal `query:"dqv"`
DefaultedPtrVal *defaultFromSchemaVal `json:"dpv,omitempty"`
DefaultedVal defaultFromSchemaVal `json:"dv"`
DefaultedTagVal defaultFromSchemaVal `query:"dtv" default:"none"`
}

df := request.NewDecoderFactory()
df.ApplyDefaults = true
df.JSONSchemaReflector = &jsonschema.Reflector{}

var input NewThing
dec := df.MakeDecoder(http.MethodPost, input, nil)

req, err := http.NewRequest(http.MethodPost, "/foo", bytes.NewReader([]byte(`{}`)))
require.NoError(t, err)

require.NoError(t, dec.Decode(req, &input, nil))
assert.Equal(t, enum1, string(*input.DefaultedPtr))
assert.Equal(t, enum1, string(input.Defaulted))
assert.Equal(t, enum1, string(*input.DefaultedQuery))
assert.Equal(t, enum2, string(input.DefaultedTag))

assert.Equal(t, enum1, string(*input.DefaultedPtrVal))
assert.Equal(t, enum1, string(input.DefaultedVal))
assert.Equal(t, enum1, string(*input.DefaultedQueryVal))
assert.Equal(t, enum2, string(input.DefaultedTagVal))
}
1 change: 1 addition & 0 deletions web/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func NewService(refl oapi.Reflector, options ...func(s *Service)) *Service {
if s.DecoderFactory == nil {
decoderFactory := request.NewDecoderFactory()
decoderFactory.ApplyDefaults = true
decoderFactory.JSONSchemaReflector = s.OpenAPICollector.Refl().JSONSchemaReflector()
decoderFactory.SetDecoderFunc(rest.ParamInPath, chirouter.PathToURLValues)

s.DecoderFactory = decoderFactory
Expand Down

0 comments on commit a631dc6

Please sign in to comment.