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

Infer default values from prepared schema #195

Merged
merged 1 commit into from
Mar 6, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"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 @@
// 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) 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 @@
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())

Check notice on line 292 in request/factory.go

View workflow job for this annotation

GitHub Actions / test (1.22.x)

1 statement(s) on lines 291:292 are not covered by tests.

Check warning on line 292 in request/factory.go

View check run for this annotation

Codecov / codecov/patch

request/factory.go#L292

Added line #L292 was not covered by tests
}

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

Check notice on line 298 in request/factory.go

View workflow job for this annotation

GitHub Actions / test (1.22.x)

1 statement(s) on lines 297:298 are not covered by tests.

Check warning on line 298 in request/factory.go

View check run for this annotation

Codecov / codecov/patch

request/factory.go#L298

Added line #L298 was not covered by tests
}

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
Loading