diff --git a/README.md b/README.md index 9d2cd0193..c8431f807 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 0c083312d..1ab693b0f 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/issue624_test.go b/openapi3filter/issue624_test.go new file mode 100644 index 000000000..d93682e52 --- /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) +} diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index 73eb73e2b..515d09cd1 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 4f2232645..b09987f74 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) } }