Repository: getkin/kin-openapi
Pull request #631 (2022-10-12 09:57:58)

openapi3filter: Fallback to string when decoding request parameters
Solves #624 


Patch:
diff --git a/README.md b/README.md
index 9d2cd01..c8431f8 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,10 @@
 [![Join Gitter Chat Channel -](https://badges.gitter.im/getkin/kin.svg)](https://gitter.im/getkin/kin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 
 # Introduction
-A [Go](https://golang.org) project for handling [OpenAPI](https://www.openapis.org/) files. We target the latest OpenAPI version (currently 3), but the project contains support for older OpenAPI versions too.
+A [Go](https://golang.org) project for handling [OpenAPI](https://www.openapis.org/) files. We target:
+* [OpenAPI `v2.0`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) (formerly known as Swagger)
+* [OpenAPI `v3.0`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md)
+* [OpenAPI `v3.1`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md) Soon! [Tracking issue here.](https://github.com/getkin/kin-openapi/issues/230)
 
 Licensed under the [MIT License](./LICENSE).
 
diff --git a/openapi3/loader.go b/openapi3/loader.go
index 0c08331..1ab693b 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -56,7 +56,9 @@ type Loader struct {
 
 // NewLoader returns an empty Loader
 func NewLoader() *Loader {
-	return &Loader{}
+	return &Loader{
+		Context: context.Background(),
+	}
 }
 
 func (loader *Loader) resetVisitedPathItemRefs() {
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 73eb73e..515d09c 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -158,7 +158,10 @@ func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationI
 }
 
 func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) (
-	outValue interface{}, outSchema *openapi3.Schema, err error) {
+	outValue interface{},
+	outSchema *openapi3.Schema,
+	err error,
+) {
 	// Only query parameters can have multiple values.
 	if len(values) > 1 && param.In != openapi3.ParameterInQuery {
 		err = fmt.Errorf("%s parameter %q cannot have multiple values", param.In, param.Name)
@@ -170,7 +173,6 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string)
 		err = fmt.Errorf("parameter %q expected to have content", param.Name)
 		return
 	}
-
 	// We only know how to decode a parameter if it has one content, application/json
 	if len(content) != 1 {
 		err = fmt.Errorf("multiple content types for parameter %q", param.Name)
@@ -184,8 +186,20 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string)
 	}
 	outSchema = mt.Schema.Value
 
+	unmarshal := func(encoded string) (decoded interface{}, err error) {
+		if err = json.Unmarshal([]byte(encoded), &decoded); err != nil {
+			const specialJSONChars = `[]{}":,`
+			if !strings.ContainsAny(encoded, specialJSONChars) {
+				// A string in a query parameter is not serialized with (double) quotes
+				// as JSON would expect, so let's fallback to that.
+				decoded, err = encoded, nil
+			}
+		}
+		return
+	}
+
 	if len(values) == 1 {
-		if err = json.Unmarshal([]byte(values[0]), &outValue); err != nil {
+		if outValue, err = unmarshal(values[0]); err != nil {
 			err = fmt.Errorf("error unmarshaling parameter %q", param.Name)
 			return
 		}
@@ -193,7 +207,7 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string)
 		outArray := make([]interface{}, 0, len(values))
 		for _, v := range values {
 			var item interface{}
-			if err = json.Unmarshal([]byte(v), &item); err != nil {
+			if item, err = unmarshal(v); err != nil {
 				err = fmt.Errorf("error unmarshaling parameter %q", param.Name)
 				return
 			}
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 4f22326..b09987f 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -148,7 +148,9 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param
 		value = schema.Default
 		req := input.Request
 		switch parameter.In {
-		// case openapi3.ParameterInPath: TODO: no idea how to handle this
+		case openapi3.ParameterInPath:
+			// Path parameters are required.
+			// Next check `parameter.Required && !found` will catch this.
 		case openapi3.ParameterInQuery:
 			q := req.URL.Query()
 			q.Add(parameter.Name, fmt.Sprintf("%v", value))
@@ -160,8 +162,6 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param
 				Name:  parameter.Name,
 				Value: fmt.Sprintf("%v", value),
 			})
-		default:
-			return fmt.Errorf("unsupported parameter's 'in': %s", parameter.In)
 		}
 	}
 


Test patch:
diff --git a/openapi3filter/issue624_test.go b/openapi3filter/issue624_test.go
new file mode 100644
index 0000000..d93682e
--- /dev/null
+++ b/openapi3filter/issue624_test.go
@@ -0,0 +1,64 @@
+package openapi3filter
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestIssue624(t *testing.T) {
+	loader := openapi3.NewLoader()
+	ctx := loader.Context
+	spec := `
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: Sample API
+paths:
+  /items:
+    get:
+      description: Returns a list of stuff
+      parameters:
+      - description: "test non object"
+        explode: true
+        style: form
+        in: query
+        name: test
+        required: false
+        content:
+          application/json:
+            schema:
+              anyOf:
+              - type: string
+              - type: integer
+      responses:
+        '200':
+          description: Successful response
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(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, `/items?test=test1`, nil)
+	require.NoError(t, err)
+
+	route, pathParams, err := router.FindRoute(httpReq)
+	require.NoError(t, err)
+
+	requestValidationInput := &RequestValidationInput{
+		Request:    httpReq,
+		PathParams: pathParams,
+		Route:      route,
+	}
+	err = ValidateRequest(ctx, requestValidationInput)
+	require.NoError(t, err)
+}