From 6e233af317f2505016f2824d3d5df0fa4eddc5fa Mon Sep 17 00:00:00 2001 From: Katsumi Kato Date: Sat, 28 Jan 2023 21:20:15 +0900 Subject: [PATCH] openapi3: fix integer enum schema validation after json.Number PR (#755) --- openapi3/schema.go | 16 ++- openapi3filter/validation_enum_test.go | 175 +++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 openapi3filter/validation_enum_test.go diff --git a/openapi3/schema.go b/openapi3/schema.go index 415c28170..4e2bf1419 100644 --- a/openapi3/schema.go +++ b/openapi3/schema.go @@ -1143,8 +1143,20 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, value interface{}) (err error) { if enum := schema.Enum; len(enum) != 0 { for _, v := range enum { - if reflect.DeepEqual(v, value) { - return + switch c := value.(type) { + case json.Number: + var f float64 + f, err = strconv.ParseFloat(c.String(), 64) + if err != nil { + return err + } + if v == f { + return + } + default: + if reflect.DeepEqual(v, value) { + return + } } } if settings.failfast { diff --git a/openapi3filter/validation_enum_test.go b/openapi3filter/validation_enum_test.go new file mode 100644 index 000000000..898c4027a --- /dev/null +++ b/openapi3filter/validation_enum_test.go @@ -0,0 +1,175 @@ +package openapi3filter + +import ( + "bytes" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" + legacyrouter "github.com/getkin/kin-openapi/routers/legacy" +) + +func TestValidationWithIntegerEnum(t *testing.T) { + const spec = ` +openapi: 3.0.0 +info: + title: Example integer enum + version: '0.1' +paths: + /sample: + put: + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + exenum: + type: integer + enum: + - 0 + - 1 + - 2 + - 3 + example: 0 + nullable: true + responses: + '200': + description: Ok +` + + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + + router, err := legacyrouter.NewRouter(doc) + require.NoError(t, err) + + tests := []struct { + data []byte + wantErr bool + }{ + { + []byte(`{"exenum": 1}`), + false, + }, + { + []byte(`{"exenum": "1"}`), + true, + }, + { + []byte(`{"exenum": null}`), + false, + }, + { + []byte(`{}`), + false, + }, + } + + for _, tt := range tests { + body := bytes.NewReader(tt.data) + req, err := http.NewRequest("PUT", "/sample", body) + require.NoError(t, err) + req.Header.Add(headerCT, "application/json") + + route, pathParams, err := router.FindRoute(req) + require.NoError(t, err) + + requestValidationInput := &RequestValidationInput{ + Request: req, + PathParams: pathParams, + Route: route, + } + err = ValidateRequest(loader.Context, requestValidationInput) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } +} + +func TestValidationWithStringEnum(t *testing.T) { + const spec = ` +openapi: 3.0.0 +info: + title: Example string enum + version: '0.1' +paths: + /sample: + put: + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + exenum: + type: string + enum: + - "0" + - "1" + - "2" + - "3" + example: "0" + responses: + '200': + description: Ok +` + + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + + router, err := legacyrouter.NewRouter(doc) + require.NoError(t, err) + + tests := []struct { + data []byte + wantErr bool + }{ + { + []byte(`{"exenum": "1"}`), + false, + }, + { + []byte(`{"exenum": 1}`), + true, + }, + { + []byte(`{"exenum": null}`), + true, + }, + { + []byte(`{}`), + false, + }, + } + + for _, tt := range tests { + body := bytes.NewReader(tt.data) + req, err := http.NewRequest("PUT", "/sample", body) + require.NoError(t, err) + req.Header.Add(headerCT, "application/json") + + route, pathParams, err := router.FindRoute(req) + require.NoError(t, err) + + requestValidationInput := &RequestValidationInput{ + Request: req, + PathParams: pathParams, + Route: route, + } + err = ValidateRequest(loader.Context, requestValidationInput) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } +}