Repository: getkin/kin-openapi
Pull request #790 (2023-05-04 20:06:04)

openapi3filter: fix oneOF validation in query params
Addresses issue raised here https://github.com/getkin/kin-openapi/issues/789. The fix differs validation to within the schema as the current implemention would always validate incorrectly. I've added test cases for the original issue, along with removing a test within `validation_test.go` which would always fail, so wasn't actually testing the intended behaviour.


Patch:
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 384b512..fd28c2f 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -301,10 +301,8 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 				isMatched++
 			}
 		}
-		if isMatched == 1 {
+		if isMatched >= 1 {
 			return value, found, nil
-		} else if isMatched > 1 {
-			return nil, found, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
 		}
 		if required {
 			return nil, found, fmt.Errorf("decoding oneOf failed: %q is required", param)
diff --git a/openapi3gen/field_info.go b/openapi3gen/field_info.go
index 13f5ba0..8c03edb 100644
--- a/openapi3gen/field_info.go
+++ b/openapi3gen/field_info.go
@@ -23,6 +23,10 @@ func appendFields(fields []theFieldInfo, parentIndex []int, t reflect.Type) []th
 	if t.Kind() == reflect.Ptr {
 		t = t.Elem()
 	}
+	if t.Kind() != reflect.Struct {
+		return fields
+	}
+
 	// For each field
 	numField := t.NumField()
 iteration:


Test patch:
diff --git a/openapi3filter/issue789_test.go b/openapi3filter/issue789_test.go
new file mode 100644
index 0000000..8e5c839
--- /dev/null
+++ b/openapi3filter/issue789_test.go
@@ -0,0 +1,126 @@
+package openapi3filter_test
+
+import (
+	"net/http"
+	"strings"
+	"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 TestIssue789(t *testing.T) {
+	anyOfArraySpec := `
+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: true
+        schema:
+          type: string
+          anyOf:
+            - pattern: '\babc\b'
+            - pattern: '\bfoo\b'
+            - pattern: '\bbar\b'
+      responses:
+        '200':
+          description: Successful response
+`[1:]
+
+	oneOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "oneOf")
+
+	allOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "allOf")
+
+	tests := []struct {
+		name   string
+		spec   string
+		req    string
+		errStr string
+	}{
+		{
+			name: "success anyof string pattern match",
+			spec: anyOfArraySpec,
+			req:  "/items?test=abc",
+		},
+		{
+			name:   "failed anyof string pattern match",
+			spec:   anyOfArraySpec,
+			req:    "/items?test=def",
+			errStr: `parameter "test" in query has an error: doesn't match any schema from "anyOf"`,
+		},
+		{
+			name: "success allof object array",
+			spec: allOfArraySpec,
+			req:  `/items?test=abc foo bar`,
+		},
+		{
+			name:   "failed allof object array",
+			spec:   allOfArraySpec,
+			req:    `/items?test=foo`,
+			errStr: `parameter "test" in query has an error: string doesn't match the regular expression`,
+		},
+		{
+			name: "success oneof string pattern match",
+			spec: oneOfArraySpec,
+			req:  `/items?test=foo`,
+		},
+		{
+			name:   "failed oneof string pattern match",
+			spec:   oneOfArraySpec,
+			req:    `/items?test=def`,
+			errStr: `parameter "test" in query has an error: doesn't match schema due to: string doesn't match the regular expression`,
+		},
+		{
+			name:   "failed oneof string pattern match",
+			spec:   oneOfArraySpec,
+			req:    `/items?test=foo bar`,
+			errStr: `parameter "test" in query has an error: input matches more than one oneOf schemas`,
+		},
+	}
+
+	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)
+			if testcase.errStr == "" {
+				require.NoError(t, err)
+			} else {
+				require.Contains(t, err.Error(), testcase.errStr)
+			}
+		},
+		)
+	}
+}
diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go
index d3a1b45..6883091 100644
--- a/openapi3filter/validation_test.go
+++ b/openapi3filter/validation_test.go
@@ -268,13 +268,6 @@ func TestFilter(t *testing.T) {
 	err = expect(req, resp)
 	require.IsType(t, &RequestError{}, err)
 
-	req = ExampleRequest{
-		Method: "POST",
-		URL:    "http://example.com/api/prefix/v/suffix?queryArgOneOf=567",
-	}
-	err = expect(req, resp)
-	require.IsType(t, &RequestError{}, err)
-
 	req = ExampleRequest{
 		Method: "POST",
 		URL:    "http://example.com/api/prefix/v/suffix?queryArgOneOf=2017-12-31T11:59:59",
diff --git a/openapi3gen/openapi3gen_test.go b/openapi3gen/openapi3gen_test.go
index 9a143e4..553affc 100644
--- a/openapi3gen/openapi3gen_test.go
+++ b/openapi3gen/openapi3gen_test.go
@@ -24,6 +24,12 @@ func ExampleGenerator_SchemaRefs() {
 	type Embedded2 struct {
 		A string `json:"a"`
 	}
+	type EmbeddedNonStruct string
+	type EmbeddedNonStructPtr string
+	type Embedded3 struct {
+		EmbeddedNonStruct
+		*EmbeddedNonStructPtr
+	}
 	type SomeStruct struct {
 		Bool    bool                      `json:"bool"`
 		Int     int                       `json:"int"`
@@ -48,6 +54,8 @@ func ExampleGenerator_SchemaRefs() {
 
 		Embedded2
 
+		Embedded3 `json:"embedded3"`
+
 		Ptr *SomeOtherType `json:"ptr"`
 	}
 
@@ -64,7 +72,7 @@ func ExampleGenerator_SchemaRefs() {
 	}
 	fmt.Printf("schemaRef: %s\n", data)
 	// Output:
-	// g.SchemaRefs: 16
+	// g.SchemaRefs: 17
 	// schemaRef: {
 	//   "properties": {
 	//     "a": {
@@ -85,6 +93,7 @@ func ExampleGenerator_SchemaRefs() {
 	//       },
 	//       "type": "object"
 	//     },
+	//     "embedded3": {},
 	//     "float64": {
 	//       "format": "double",
 	//       "type": "number"