Repository: getkin/kin-openapi
Pull request #1000 (2024-08-01 18:54:22)

openapi3filter: Fix default value for array in for query param
after talking with @TheSadlig we noticed that the behavior was not working as expected.

I removed the code that @TheSadlig introduced as we found out that only one `[]interface{}` case was needed. 
I also added a test with a working openapi contract that match our problem to ensure the behavior was working as expected for our case. 

cf: https://github.com/getkin/kin-openapi/pull/992


fixes https://github.com/getkin/kin-openapi/issues/991

ps: it is the 1000 PR, so many people involved !


Patch:
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 296403c..3892a39 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -114,11 +114,7 @@ func appendToQueryValues[T any](q url.Values, parameterName string, v []T) {
 // populateDefaultQueryParameters populates default values inside query parameters, while ensuring types are respected
 func populateDefaultQueryParameters(q url.Values, parameterName string, value any) {
 	switch t := value.(type) {
-	case []string:
-		appendToQueryValues(q, parameterName, t)
-	case []float64:
-		appendToQueryValues(q, parameterName, t)
-	case []int:
+	case []interface{}:
 		appendToQueryValues(q, parameterName, t)
 	default:
 		q.Add(parameterName, fmt.Sprintf("%v", value))


Test patch:
diff --git a/openapi3filter/issue991_test.go b/openapi3filter/issue991_test.go
new file mode 100644
index 0000000..a81b5b5
--- /dev/null
+++ b/openapi3filter/issue991_test.go
@@ -0,0 +1,119 @@
+package openapi3filter
+
+import (
+	"context"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateRequestDefault(t *testing.T) {
+	const spec = `
+openapi: 3.0.0
+info:
+  title: 'Validator'
+  version: 0.0.1
+paths:
+  /category:
+    get:
+      parameters:
+        - $ref: "#/components/parameters/Type"
+      responses:
+        '200':
+          description: Ok
+components:
+  parameters:
+    Type:
+      in: query
+      name: type
+      required: false
+      description: Type parameter
+      schema:
+        type: array
+        default:
+          - A
+          - B
+          - C
+        items:
+          type: string
+          enum:
+            - A
+            - B
+            - C
+`
+
+	router := setupTestRouter(t, spec)
+
+	type args struct {
+		url      string
+		expected []string
+	}
+	tests := []struct {
+		name                 string
+		args                 args
+		expectedModification bool
+		expectedErr          error
+	}{
+		{
+			name: "Valid request without type parameters set",
+			args: args{
+				url:      "/category",
+				expected: []string{"A", "B", "C"},
+			},
+			expectedModification: false,
+			expectedErr:          nil,
+		},
+		{
+			name: "Valid request with 1 type parameters set",
+			args: args{
+				url:      "/category?type=A",
+				expected: []string{"A"},
+			},
+			expectedModification: false,
+			expectedErr:          nil,
+		},
+		{
+			name: "Valid request with 2 type parameters set",
+			args: args{
+				url:      "/category?type=A&type=C",
+				expected: []string{"A", "C"},
+			},
+			expectedModification: false,
+			expectedErr:          nil,
+		},
+		{
+			name: "Valid request with 1 type parameters set out of enum",
+			args: args{
+				url:      "/category?type=X",
+				expected: nil,
+			},
+			expectedModification: false,
+			expectedErr:          &RequestError{},
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+
+			req, err := http.NewRequest(http.MethodGet, tc.args.url, nil)
+			require.NoError(t, err)
+
+			route, pathParams, err := router.FindRoute(req)
+			require.NoError(t, err)
+
+			validationInput := &RequestValidationInput{
+				Request:    req,
+				PathParams: pathParams,
+				Route:      route,
+			}
+			err = ValidateRequest(context.Background(), validationInput)
+			assert.IsType(t, tc.expectedErr, err, "ValidateRequest(): error = %v, expectedError %v", err, tc.expectedErr)
+			if tc.expectedErr != nil {
+				return
+			}
+
+			assert.Equal(t, tc.args.expected, req.URL.Query()["type"], "ValidateRequest(): query parameter type values do not match expected")
+		})
+	}
+}
diff --git a/openapi3filter/validate_request_test.go b/openapi3filter/validate_request_test.go
index 505e5cb..474fab3 100644
--- a/openapi3filter/validate_request_test.go
+++ b/openapi3filter/validate_request_test.go
@@ -567,94 +567,3 @@ paths:
 	})
 	require.Error(t, err)
 }
-
-var (
-	StringArraySchemaWithDefault = &openapi3.SchemaRef{
-		Value: &openapi3.Schema{
-			Type:    &openapi3.Types{"array"},
-			Items:   stringSchema,
-			Default: []string{"A", "B", "C"},
-		},
-	}
-	FloatArraySchemaWithDefault = &openapi3.SchemaRef{
-		Value: &openapi3.Schema{
-			Type:    &openapi3.Types{"array"},
-			Items:   numberSchema,
-			Default: []float64{1.5, 2.5, 3.5},
-		},
-	}
-)
-
-func TestValidateRequestDefault(t *testing.T) {
-	type testCase struct {
-		name       string
-		param      *openapi3.Parameter
-		query      string
-		wantQuery  map[string][]string
-		wantHeader map[string]any
-	}
-
-	testCases := []testCase{
-		{
-			name: "String Array In Query",
-			param: &openapi3.Parameter{
-				Name: "param", In: "query", Style: "form", Explode: explode,
-				Schema: StringArraySchemaWithDefault,
-			},
-			wantQuery: map[string][]string{
-				"param": {
-					"A",
-					"B",
-					"C",
-				},
-			},
-		},
-		{
-			name: "Float Array In Query",
-			param: &openapi3.Parameter{
-				Name: "param", In: "query", Style: "form", Explode: explode,
-				Schema: FloatArraySchemaWithDefault,
-			},
-			wantQuery: map[string][]string{
-				"param": {
-					"1.5",
-					"2.5",
-					"3.5",
-				},
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			info := &openapi3.Info{
-				Title:   "MyAPI",
-				Version: "0.1",
-			}
-			doc := &openapi3.T{OpenAPI: "3.0.0", Info: info, Paths: openapi3.NewPaths()}
-			op := &openapi3.Operation{
-				OperationID: "test",
-				Parameters:  []*openapi3.ParameterRef{{Value: tc.param}},
-				Responses:   openapi3.NewResponses(),
-			}
-			doc.AddOperation("/test", http.MethodGet, op)
-			err := doc.Validate(context.Background())
-			require.NoError(t, err)
-			router, err := legacyrouter.NewRouter(doc)
-			require.NoError(t, err)
-
-			req, err := http.NewRequest(http.MethodGet, "http://test.org/test?"+tc.query, nil)
-			route, pathParams, err := router.FindRoute(req)
-			require.NoError(t, err)
-
-			input := &RequestValidationInput{Request: req, PathParams: pathParams, Route: route}
-
-			err = ValidateParameter(context.Background(), input, tc.param)
-			require.NoError(t, err)
-
-			for k, v := range tc.wantQuery {
-				require.Equal(t, v, input.Request.URL.Query()[k])
-			}
-		})
-	}
-}