From 0e90bc1b5810b481a129277f2adc7cc5a46675fa Mon Sep 17 00:00:00 2001 From: solomon Date: Tue, 15 Nov 2022 14:20:14 +0000 Subject: [PATCH 1/4] fix: query param pattern --- openapi3filter/issue641_test.go | 129 +++++++++++++++++++++++++++++ openapi3filter/req_resp_decoder.go | 18 ++++ 2 files changed, 147 insertions(+) create mode 100644 openapi3filter/issue641_test.go diff --git a/openapi3filter/issue641_test.go b/openapi3filter/issue641_test.go new file mode 100644 index 000000000..456cdfd5c --- /dev/null +++ b/openapi3filter/issue641_test.go @@ -0,0 +1,129 @@ +package openapi3filter_test + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/getkin/kin-openapi/routers/gorillamux" +) + +func TestIssue625(t *testing.T) { + + anyOfSpec := ` +openapi: 3.0.0 +info: + version: 1.0.0 + title: Sample API +paths: + /items: + get: + description: Returns a list of stuff + parameters: + - description: test object + explode: false + in: query + name: test + required: false + schema: + anyOf: + - pattern: "^[0-9]{1,4}$" + - pattern: "^[0-9]{1,4}$" + type: string + responses: + '200': + description: Successful response +`[1:] + + allOfSpec := ` +openapi: 3.0.0 +info: + version: 1.0.0 + title: Sample API +paths: + /items: + get: + description: Returns a list of stuff + parameters: + - description: test object + explode: false + in: query + name: test + required: false + schema: + allOf: + - pattern: "^[0-9]{1,4}$" + - pattern: "^[0-9]{1,4}$" + type: string + responses: + '200': + description: Successful response +`[1:] + + tests := []struct { + name string + spec string + req string + isErr bool + }{ + + { + name: "success anyof pattern", + spec: anyOfSpec, + req: "/items?test=51", + isErr: false, + }, + { + name: "failed anyof pattern", + spec: anyOfSpec, + req: "/items?test=999999", + isErr: true, + }, + + { + name: "success allof pattern", + spec: allOfSpec, + req: `/items?test=51`, + isErr: false, + }, + { + name: "failed allof pattern", + spec: allOfSpec, + req: `/items?test=999999`, + isErr: true, + }, + } + + for _, testcase := range tests { + t.Run(testcase.name, func(t *testing.T) { + loader := openapi3.NewLoader() + ctx := loader.Context + + doc, err := loader.LoadFromData([]byte(testcase.spec)) + require.NoError(t, err) + + err = doc.Validate(ctx) + require.NoError(t, err) + + router, err := gorillamux.NewRouter(doc) + require.NoError(t, err) + httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil) + require.NoError(t, err) + + route, pathParams, err := router.FindRoute(httpReq) + require.NoError(t, err) + + requestValidationInput := &openapi3filter.RequestValidationInput{ + Request: httpReq, + PathParams: pathParams, + Route: route, + } + err = openapi3filter.ValidateRequest(ctx, requestValidationInput) + require.Equal(t, testcase.isErr, err != nil) + }, + ) + } +} diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index ca8194432..cfc41995c 100644 --- a/openapi3filter/req_resp_decoder.go +++ b/openapi3filter/req_resp_decoder.go @@ -256,6 +256,22 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn return decodeValue(dec, param.Name, sm, param.Schema, param.Required) } +/** +addSchemaTypeIfNeeded add the schema type to the schema if +it can be concluded. +If the schema contain "pattern" without type and the parent has a string type, +then the type of the schema must be string as well +*/ +func addSchemaTypeIfNeeded(parentType string, schema *openapi3.SchemaRef) { + if (schema != nil) && (parentType == "string") { + + if (schema.Value.Pattern != "") && + (schema.Value.Type == "") { + schema.Value.Type = "string" + } + } +} + func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, bool, error) { var found bool @@ -264,6 +280,7 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho var err error for _, sr := range schema.Value.AllOf { var f bool + addSchemaTypeIfNeeded(schema.Value.Type, sr) value, f, err = decodeValue(dec, param, sm, sr, required) found = found || f if value == nil || err != nil { @@ -275,6 +292,7 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho if len(schema.Value.AnyOf) > 0 { for _, sr := range schema.Value.AnyOf { + addSchemaTypeIfNeeded(schema.Value.Type, sr) value, f, _ := decodeValue(dec, param, sm, sr, required) found = found || f if value != nil { From 1e28da0db3c0108bf7fb29c3b196d0afbf547869 Mon Sep 17 00:00:00 2001 From: solomon Date: Tue, 15 Nov 2022 14:27:59 +0000 Subject: [PATCH 2/4] fix: query param pattern --- openapi3filter/req_resp_decoder.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index cfc41995c..a3d6262de 100644 --- a/openapi3filter/req_resp_decoder.go +++ b/openapi3filter/req_resp_decoder.go @@ -256,12 +256,10 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn return decodeValue(dec, param.Name, sm, param.Schema, param.Required) } -/** -addSchemaTypeIfNeeded add the schema type to the schema if -it can be concluded. -If the schema contain "pattern" without type and the parent has a string type, -then the type of the schema must be string as well -*/ +// addSchemaTypeIfNeeded add the schema type to the schema if +// it can be concluded. +// If the schema contain pattern without type and the parent has a string type, +// then the type of the schema must be string as well func addSchemaTypeIfNeeded(parentType string, schema *openapi3.SchemaRef) { if (schema != nil) && (parentType == "string") { From 8278b11467fd48f47be037e330eba82137b7c5ae Mon Sep 17 00:00:00 2001 From: solomon Date: Tue, 15 Nov 2022 14:39:07 +0000 Subject: [PATCH 3/4] fix: query param pattern --- openapi3filter/req_resp_decoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index a3d6262de..6c7a57163 100644 --- a/openapi3filter/req_resp_decoder.go +++ b/openapi3filter/req_resp_decoder.go @@ -256,7 +256,7 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn return decodeValue(dec, param.Name, sm, param.Schema, param.Required) } -// addSchemaTypeIfNeeded add the schema type to the schema if +// addSchemaTypeIfNeeded adds the schema type to the schema if // it can be concluded. // If the schema contain pattern without type and the parent has a string type, // then the type of the schema must be string as well From 8b295070a4bd16732d1b919f898ee8b2e74dc497 Mon Sep 17 00:00:00 2001 From: solomon Date: Thu, 17 Nov 2022 06:44:11 +0000 Subject: [PATCH 4/4] fix: query with pattern --- openapi3filter/issue641_test.go | 72 +++++++++++------------------- openapi3filter/req_resp_decoder.go | 23 +++------- 2 files changed, 33 insertions(+), 62 deletions(-) diff --git a/openapi3filter/issue641_test.go b/openapi3filter/issue641_test.go index 456cdfd5c..3e0bba22e 100644 --- a/openapi3filter/issue641_test.go +++ b/openapi3filter/issue641_test.go @@ -2,6 +2,7 @@ package openapi3filter_test import ( "net/http" + "strings" "testing" "github.com/stretchr/testify/require" @@ -11,7 +12,7 @@ import ( "github.com/getkin/kin-openapi/routers/gorillamux" ) -func TestIssue625(t *testing.T) { +func TestIssue641(t *testing.T) { anyOfSpec := ` openapi: 3.0.0 @@ -38,62 +39,37 @@ paths: description: Successful response `[1:] - allOfSpec := ` -openapi: 3.0.0 -info: - version: 1.0.0 - title: Sample API -paths: - /items: - get: - description: Returns a list of stuff - parameters: - - description: test object - explode: false - in: query - name: test - required: false - schema: - allOf: - - pattern: "^[0-9]{1,4}$" - - pattern: "^[0-9]{1,4}$" - type: string - responses: - '200': - description: Successful response -`[1:] + allOfSpec := strings.ReplaceAll(anyOfSpec, "anyOf", "allOf") tests := []struct { - name string - spec string - req string - isErr bool + name string + spec string + req string + errStr string }{ { - name: "success anyof pattern", - spec: anyOfSpec, - req: "/items?test=51", - isErr: false, + name: "success anyof pattern", + spec: anyOfSpec, + req: "/items?test=51", }, { - name: "failed anyof pattern", - spec: anyOfSpec, - req: "/items?test=999999", - isErr: true, + name: "failed anyof pattern", + spec: anyOfSpec, + req: "/items?test=999999", + errStr: `parameter "test" in query has an error: Doesn't match schema "anyOf"`, }, { - name: "success allof pattern", - spec: allOfSpec, - req: `/items?test=51`, - isErr: false, + name: "success allof pattern", + spec: allOfSpec, + req: `/items?test=51`, }, { - name: "failed allof pattern", - spec: allOfSpec, - req: `/items?test=999999`, - isErr: true, + name: "failed allof pattern", + spec: allOfSpec, + req: `/items?test=999999`, + errStr: `parameter "test" in query has an error: string doesn't match the regular expression`, }, } @@ -122,7 +98,11 @@ paths: Route: route, } err = openapi3filter.ValidateRequest(ctx, requestValidationInput) - require.Equal(t, testcase.isErr, err != nil) + if testcase.errStr == "" { + require.NoError(t, err) + } else { + require.Contains(t, err.Error(), testcase.errStr) + } }, ) } diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index 6c7a57163..f98a51048 100644 --- a/openapi3filter/req_resp_decoder.go +++ b/openapi3filter/req_resp_decoder.go @@ -256,20 +256,6 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn return decodeValue(dec, param.Name, sm, param.Schema, param.Required) } -// addSchemaTypeIfNeeded adds the schema type to the schema if -// it can be concluded. -// If the schema contain pattern without type and the parent has a string type, -// then the type of the schema must be string as well -func addSchemaTypeIfNeeded(parentType string, schema *openapi3.SchemaRef) { - if (schema != nil) && (parentType == "string") { - - if (schema.Value.Pattern != "") && - (schema.Value.Type == "") { - schema.Value.Type = "string" - } - } -} - func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, bool, error) { var found bool @@ -278,7 +264,6 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho var err error for _, sr := range schema.Value.AllOf { var f bool - addSchemaTypeIfNeeded(schema.Value.Type, sr) value, f, err = decodeValue(dec, param, sm, sr, required) found = found || f if value == nil || err != nil { @@ -290,7 +275,6 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho if len(schema.Value.AnyOf) > 0 { for _, sr := range schema.Value.AnyOf { - addSchemaTypeIfNeeded(schema.Value.Type, sr) value, f, _ := decodeValue(dec, param, sm, sr, required) found = found || f if value != nil { @@ -350,6 +334,9 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho case *pathParamDecoder: _, found = vDecoder.pathParams[param] case *urlValuesDecoder: + if schema.Value.Pattern != "" { + return dec.DecodePrimitive(param, sm, schema) + } _, found = vDecoder.values[param] case *headerParamDecoder: _, found = vDecoder.header[param] @@ -516,6 +503,10 @@ func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.Serializat // HTTP request does not contain a value of the target query parameter. return nil, ok, nil } + + if schema.Value.Type == "" && schema.Value.Pattern != "" { + return values[0], ok, nil + } val, err := parsePrimitive(values[0], schema) return val, ok, err }