diff --git a/_examples/go.mod b/_examples/go.mod index 7b75e96..db07f3a 100644 --- a/_examples/go.mod +++ b/_examples/go.mod @@ -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 diff --git a/_examples/go.sum b/_examples/go.sum index 7c87d29..a6211c5 100644 --- a/_examples/go.sum +++ b/_examples/go.sum @@ -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= diff --git a/go.mod b/go.mod index 2fc33be..f05ba3b 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index ffe9d70..9857b9a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/request/factory.go b/request/factory.go index 940437d..8978bac 100644 --- a/request/factory.go +++ b/request/factory.go @@ -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" @@ -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 @@ -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 { @@ -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), `"`)} + } } }) diff --git a/request/factory_test.go b/request/factory_test.go index 765fdae..ebad192 100644 --- a/request/factory_test.go +++ b/request/factory_test.go @@ -1,6 +1,7 @@ package request_test import ( + "bytes" "encoding/json" "fmt" "net/http" @@ -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" ) @@ -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)) +} diff --git a/web/service.go b/web/service.go index d907caa..eb0a973 100644 --- a/web/service.go +++ b/web/service.go @@ -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