diff --git a/openapi3filter/fixtures/petstore.json b/openapi3filter/fixtures/petstore.json index 932241fc9..1a8cd8c04 100644 --- a/openapi3filter/fixtures/petstore.json +++ b/openapi3filter/fixtures/petstore.json @@ -211,6 +211,111 @@ ] } }, + "/pets/": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pets by the specified filters", + "description": "Returns a list of pets that comply with the specified filters", + "operationId": "findPets", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "allowEmptyValue": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + } + } + }, + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "kind", + "in": "query", + "description": "Kinds to filter by", + "required": false, + "explode": false, + "style": "pipeDelimited", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "dog", + "cat", + "turtle", + "bird,with,commas" + ] + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "allOf": [ + {"$ref": "#/components/schemas/Pet"}, + {"$ref": "#/components/schemas/PetRequiredProperties"} + ] + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "allOf": [ + {"$ref": "#/components/schemas/Pet"}, + {"$ref": "#/components/schemas/PetRequiredProperties"} + ] + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, "/pet/findByTags": { "get": { "tags": [ diff --git a/openapi3filter/internal.go b/openapi3filter/internal.go index facaf1de5..5c6a8a6c6 100644 --- a/openapi3filter/internal.go +++ b/openapi3filter/internal.go @@ -1,6 +1,7 @@ package openapi3filter import ( + "reflect" "strings" ) @@ -11,3 +12,14 @@ func parseMediaType(contentType string) string { } return contentType[:i] } + +func isNilValue(value interface{}) bool { + if value == nil { + return true + } + switch reflect.TypeOf(value).Kind() { + case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: + return reflect.ValueOf(value).IsNil() + } + return false +} diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index 12b368384..0408d8da3 100644 --- a/openapi3filter/req_resp_decoder.go +++ b/openapi3filter/req_resp_decoder.go @@ -110,10 +110,9 @@ func invalidSerializationMethodErr(sm *openapi3.SerializationMethod) error { // Decodes a parameter defined via the content property as an object. It uses // the user specified decoder, or our build-in decoder for application/json func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationInput) ( - value interface{}, schema *openapi3.Schema, err error) { + value interface{}, schema *openapi3.Schema, found bool, err error) { var paramValues []string - var found bool switch param.In { case openapi3.ParameterInPath: var paramValue string @@ -123,9 +122,9 @@ func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationI case openapi3.ParameterInQuery: paramValues, found = input.GetQueryParams()[param.Name] case openapi3.ParameterInHeader: - if paramValue := input.Request.Header.Get(http.CanonicalHeaderKey(param.Name)); paramValue != "" { - paramValues = []string{paramValue} - found = true + var headerValues []string + if headerValues, found = input.Request.Header[http.CanonicalHeaderKey(param.Name)]; found { + paramValues = headerValues } case openapi3.ParameterInCookie: var cookie *http.Cookie @@ -206,30 +205,30 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) } type valueDecoder interface { - DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) - DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) - DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) + DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) + DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) + DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) } // decodeStyledParameter returns a value of an operation's parameter from HTTP request for -// parameters defined using the style format. +// parameters defined using the style format, and whether the parameter is supplied in the input. // The function returns ParseError when HTTP request contains an invalid value of a parameter. -func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationInput) (interface{}, error) { +func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationInput) (interface{}, bool, error) { sm, err := param.SerializationMethod() if err != nil { - return nil, err + return nil, false, err } var dec valueDecoder switch param.In { case openapi3.ParameterInPath: if len(input.PathParams) == 0 { - return nil, nil + return nil, false, nil } dec = &pathParamDecoder{pathParams: input.PathParams} case openapi3.ParameterInQuery: if len(input.GetQueryParams()) == 0 { - return nil, nil + return nil, false, nil } dec = &urlValuesDecoder{values: input.GetQueryParams()} case openapi3.ParameterInHeader: @@ -237,73 +236,79 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn case openapi3.ParameterInCookie: dec = &cookieParamDecoder{req: input.Request} default: - return nil, fmt.Errorf("unsupported parameter's 'in': %s", param.In) + return nil, false, fmt.Errorf("unsupported parameter's 'in': %s", param.In) } return decodeValue(dec, param.Name, sm, param.Schema, param.Required) } -func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, error) { +func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, bool, error) { + var found bool + if len(schema.Value.AllOf) > 0 { var value interface{} var err error for _, sr := range schema.Value.AllOf { - value, err = decodeValue(dec, param, sm, sr, required) + var f bool + value, f, err = decodeValue(dec, param, sm, sr, required) + found = found || f if value == nil || err != nil { break } } - return value, err + return value, found, err } if len(schema.Value.AnyOf) > 0 { for _, sr := range schema.Value.AnyOf { - value, _ := decodeValue(dec, param, sm, sr, required) + value, f, _ := decodeValue(dec, param, sm, sr, required) + found = found || f if value != nil { - return value, nil + return value, found, nil } } if required { - return nil, fmt.Errorf("decoding anyOf for parameter %q failed", param) + return nil, found, fmt.Errorf("decoding anyOf for parameter %q failed", param) } - return nil, nil + return nil, found, nil } if len(schema.Value.OneOf) > 0 { isMatched := 0 var value interface{} for _, sr := range schema.Value.OneOf { - v, _ := decodeValue(dec, param, sm, sr, required) + v, f, _ := decodeValue(dec, param, sm, sr, required) + found = found || f if v != nil { value = v isMatched++ } } if isMatched == 1 { - return value, nil + return value, found, nil } else if isMatched > 1 { - return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched) + return nil, found, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched) } if required { - return nil, fmt.Errorf("decoding oneOf failed: %q is required", param) + return nil, found, fmt.Errorf("decoding oneOf failed: %q is required", param) } - return nil, nil + return nil, found, nil } if schema.Value.Not != nil { // TODO(decode not): handle decoding "not" JSON Schema - return nil, errors.New("not implemented: decoding 'not'") + return nil, found, errors.New("not implemented: decoding 'not'") } if schema.Value.Type != "" { - var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) + var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) switch schema.Value.Type { case "array": - decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) { + decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { return dec.DecodeArray(param, sm, schema) } case "object": - decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) { + decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { return dec.DecodeObject(param, sm, schema) } default: @@ -311,8 +316,20 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho } return decodeFn(param, sm, schema) } - - return nil, nil + switch vDecoder := dec.(type) { + case *pathParamDecoder: + _, found = vDecoder.pathParams[param] + case *urlValuesDecoder: + _, found = vDecoder.values[param] + case *headerParamDecoder: + _, found = vDecoder.header[param] + case *cookieParamDecoder: + _, err := vDecoder.req.Cookie(param) + found = err != http.ErrNoCookie + default: + return nil, found, errors.New("unsupported decoder") + } + return nil, found, nil } // pathParamDecoder decodes values of path parameters. @@ -320,7 +337,7 @@ type pathParamDecoder struct { pathParams map[string]string } -func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) { +func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { var prefix string switch sm.Style { case "simple": @@ -330,26 +347,27 @@ func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.Serializat case "matrix": prefix = ";" + param + "=" default: - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } if d.pathParams == nil { // HTTP request does not contains a value of the target path parameter. - return nil, nil + return nil, false, nil } raw, ok := d.pathParams[param] if !ok || raw == "" { // HTTP request does not contains a value of the target path parameter. - return nil, nil + return nil, false, nil } src, err := cutPrefix(raw, prefix) if err != nil { - return nil, err + return nil, ok, err } - return parsePrimitive(src, schema) + val, err := parsePrimitive(src, schema) + return val, ok, err } -func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) { +func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { var prefix, delim string switch { case sm.Style == "simple": @@ -367,26 +385,27 @@ func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationM prefix = ";" + param + "=" delim = ";" + param + "=" default: - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } if d.pathParams == nil { // HTTP request does not contains a value of the target path parameter. - return nil, nil + return nil, false, nil } raw, ok := d.pathParams[param] if !ok || raw == "" { // HTTP request does not contains a value of the target path parameter. - return nil, nil + return nil, false, nil } src, err := cutPrefix(raw, prefix) if err != nil { - return nil, err + return nil, ok, err } - return parseArray(strings.Split(src, delim), schema) + val, err := parseArray(strings.Split(src, delim), schema) + return val, ok, err } -func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) { +func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { var prefix, propsDelim, valueDelim string switch { case sm.Style == "simple" && !sm.Explode: @@ -412,27 +431,28 @@ func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.Serialization propsDelim = ";" valueDelim = "=" default: - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } if d.pathParams == nil { // HTTP request does not contains a value of the target path parameter. - return nil, nil + return nil, false, nil } raw, ok := d.pathParams[param] if !ok || raw == "" { // HTTP request does not contains a value of the target path parameter. - return nil, nil + return nil, false, nil } src, err := cutPrefix(raw, prefix) if err != nil { - return nil, err + return nil, ok, err } props, err := propsFromString(src, propsDelim, valueDelim) if err != nil { - return nil, err + return nil, ok, err } - return makeObject(props, schema) + val, err := makeObject(props, schema) + return val, ok, err } // cutPrefix validates that a raw value of a path parameter has the specified prefix, @@ -456,28 +476,29 @@ type urlValuesDecoder struct { values url.Values } -func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) { +func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { if sm.Style != "form" { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } - values := d.values[param] + values, ok := d.values[param] if len(values) == 0 { // HTTP request does not contain a value of the target query parameter. - return nil, nil + return nil, ok, nil } - return parsePrimitive(values[0], schema) + val, err := parsePrimitive(values[0], schema) + return val, ok, err } -func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) { +func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { if sm.Style == "deepObject" { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } - values := d.values[param] + values, ok := d.values[param] if len(values) == 0 { // HTTP request does not contain a value of the target query parameter. - return nil, nil + return nil, ok, nil } if !sm.Explode { var delim string @@ -491,10 +512,11 @@ func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationM } values = strings.Split(values[0], delim) } - return parseArray(values, schema) + val, err := parseArray(values, schema) + return val, ok, err } -func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) { +func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { var propsFn func(url.Values) (map[string]string, error) switch sm.Style { case "form": @@ -535,17 +557,27 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization return props, nil } default: - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } props, err := propsFn(d.values) if err != nil { - return nil, err + return nil, false, err } if props == nil { - return nil, nil + return nil, false, nil } - return makeObject(props, schema) + + // check the props + found := false + for propName := range schema.Value.Properties { + if _, ok := props[propName]; ok { + found = true + break + } + } + val, err := makeObject(props, schema) + return val, found, err } // headerParamDecoder decodes values of header parameters. @@ -553,47 +585,56 @@ type headerParamDecoder struct { header http.Header } -func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) { +func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { if sm.Style != "simple" { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } - raw := d.header.Get(http.CanonicalHeaderKey(param)) - return parsePrimitive(raw, schema) + raw, ok := d.header[http.CanonicalHeaderKey(param)] + if !ok || len(raw) == 0 { + // HTTP request does not contains a corresponding header or has the empty value + return nil, ok, nil + } + + val, err := parsePrimitive(raw[0], schema) + return val, ok, err } -func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) { +func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { if sm.Style != "simple" { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } - raw := d.header.Get(http.CanonicalHeaderKey(param)) - if raw == "" { + raw, ok := d.header[http.CanonicalHeaderKey(param)] + if !ok || len(raw) == 0 { // HTTP request does not contains a corresponding header - return nil, nil + return nil, ok, nil } - return parseArray(strings.Split(raw, ","), schema) + + val, err := parseArray(strings.Split(raw[0], ","), schema) + return val, ok, err } -func (d *headerParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) { +func (d *headerParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { if sm.Style != "simple" { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } valueDelim := "," if sm.Explode { valueDelim = "=" } - raw := d.header.Get(http.CanonicalHeaderKey(param)) - if raw == "" { + raw, ok := d.header[http.CanonicalHeaderKey(param)] + if !ok || len(raw) == 0 { // HTTP request does not contain a corresponding header. - return nil, nil + return nil, ok, nil } - props, err := propsFromString(raw, ",", valueDelim) + props, err := propsFromString(raw[0], ",", valueDelim) if err != nil { - return nil, err + return nil, ok, err } - return makeObject(props, schema) + val, err := makeObject(props, schema) + return val, ok, err } // cookieParamDecoder decodes values of cookie parameters. @@ -601,56 +642,63 @@ type cookieParamDecoder struct { req *http.Request } -func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) { +func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { if sm.Style != "form" { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } cookie, err := d.req.Cookie(param) - if err == http.ErrNoCookie { + found := err != http.ErrNoCookie + if !found { // HTTP request does not contain a corresponding cookie. - return nil, nil + return nil, found, nil } if err != nil { - return nil, fmt.Errorf("decoding param %q: %s", param, err) + return nil, found, fmt.Errorf("decoding param %q: %s", param, err) } - return parsePrimitive(cookie.Value, schema) + + val, err := parsePrimitive(cookie.Value, schema) + return val, found, err } -func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) { +func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { if sm.Style != "form" || sm.Explode { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } cookie, err := d.req.Cookie(param) - if err == http.ErrNoCookie { + found := err != http.ErrNoCookie + if !found { // HTTP request does not contain a corresponding cookie. - return nil, nil + return nil, found, nil } if err != nil { - return nil, fmt.Errorf("decoding param %q: %s", param, err) + return nil, found, fmt.Errorf("decoding param %q: %s", param, err) } - return parseArray(strings.Split(cookie.Value, ","), schema) + val, err := parseArray(strings.Split(cookie.Value, ","), schema) + return val, found, err } -func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) { +func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { if sm.Style != "form" || sm.Explode { - return nil, invalidSerializationMethodErr(sm) + return nil, false, invalidSerializationMethodErr(sm) } cookie, err := d.req.Cookie(param) - if err == http.ErrNoCookie { + found := err != http.ErrNoCookie + if !found { // HTTP request does not contain a corresponding cookie. - return nil, nil + return nil, found, nil } if err != nil { - return nil, fmt.Errorf("decoding param %q: %s", param, err) + return nil, found, fmt.Errorf("decoding param %q: %s", param, err) } props, err := propsFromString(cookie.Value, ",", ",") if err != nil { - return nil, err + return nil, found, err } - return makeObject(props, schema) + val, err := makeObject(props, schema) + return val, found, err } // propsFromString returns a properties map that is created by splitting a source string by propDelim and valueDelim. @@ -725,6 +773,12 @@ func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, err } return nil, fmt.Errorf("item %d: %s", i, err) } + + // If the items are nil, then the array is nil. There shouldn't be case where some values are actual primitive + // values and some are nil values. + if item == nil { + return nil, nil + } value = append(value, item) } return value, nil @@ -913,7 +967,7 @@ func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3. } sm := enc.SerializationMethod() - if value, err = decodeValue(dec, name, sm, prop, false); err != nil { + if value, _, err = decodeValue(dec, name, sm, prop, false); err != nil { return nil, err } obj[name] = value diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go index f40a7a53e..de93547b5 100644 --- a/openapi3filter/req_resp_decoder_test.go +++ b/openapi3filter/req_resp_decoder_test.go @@ -31,7 +31,7 @@ func TestDecodeParameter(t *testing.T) { objectOf = func(args ...interface{}) *openapi3.SchemaRef { s := &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "object", Properties: make(map[string]*openapi3.SchemaRef)}} if len(args)%2 != 0 { - panic("invalid arguments. must be an odd number of arguments") + panic("invalid arguments. must be an even number of arguments") } for i := 0; i < len(args)/2; i++ { propName := args[i*2].(string) @@ -75,6 +75,7 @@ func TestDecodeParameter(t *testing.T) { header string cookie string want interface{} + found bool err error } @@ -90,23 +91,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: stringSchema}, path: "/foo", want: "foo", + found: true, }, { name: "simple explode", param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: stringSchema}, path: "/foo", want: "foo", + found: true, }, { name: "label", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringSchema}, path: "/.foo", want: "foo", + found: true, }, { name: "label invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringSchema}, path: "/foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -114,11 +119,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringSchema}, path: "/.foo", want: "foo", + found: true, }, { name: "label explode invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringSchema}, path: "/foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -126,11 +133,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringSchema}, path: "/;param=foo", want: "foo", + found: true, }, { name: "matrix invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringSchema}, path: "/foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -138,11 +147,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringSchema}, path: "/;param=foo", want: "foo", + found: true, }, { name: "matrix explode invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringSchema}, path: "/foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -150,23 +161,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Schema: stringSchema}, path: "/foo", want: "foo", + found: true, }, { name: "string", param: &openapi3.Parameter{Name: "param", In: "path", Schema: stringSchema}, path: "/foo", want: "foo", + found: true, }, { name: "integer", param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema}, path: "/1", want: float64(1), + found: true, }, { name: "integer invalid", param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema}, path: "/foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -174,11 +189,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema}, path: "/1.1", want: 1.1, + found: true, }, { name: "number invalid", param: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema}, path: "/foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -186,11 +203,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema}, path: "/true", want: true, + found: true, }, { name: "boolean invalid", param: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema}, path: "/foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, }, @@ -203,23 +222,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: arraySchema}, path: "/foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "simple explode", param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: arraySchema}, path: "/foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "label", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: arraySchema}, path: "/.foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "label invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: arraySchema}, path: "/foo,bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo,bar"}, }, { @@ -227,11 +250,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: arraySchema}, path: "/.foo.bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "label explode invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: arraySchema}, path: "/foo.bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo.bar"}, }, { @@ -239,11 +264,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: arraySchema}, path: "/;param=foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "matrix invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: arraySchema}, path: "/foo,bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo,bar"}, }, { @@ -251,11 +278,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: arraySchema}, path: "/;param=foo;param=bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "matrix explode invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: arraySchema}, path: "/foo,bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo,bar"}, }, { @@ -263,23 +292,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Schema: arraySchema}, path: "/foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "invalid integer items", param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(integerSchema)}, path: "/1,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid number items", param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(numberSchema)}, path: "/1.1,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid boolean items", param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(booleanSchema)}, path: "/true,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, }, @@ -292,23 +325,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: objectSchema}, path: "/id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "simple explode", param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: objectSchema}, path: "/id=foo,name=bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "label", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: objectSchema}, path: "/.id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "label invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: objectSchema}, path: "/id,foo,name,bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "id,foo,name,bar"}, }, { @@ -316,11 +353,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: objectSchema}, path: "/.id=foo.name=bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "label explode invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: objectSchema}, path: "/id=foo.name=bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "id=foo.name=bar"}, }, { @@ -328,11 +367,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: objectSchema}, path: "/;param=id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "matrix invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: objectSchema}, path: "/id,foo,name,bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "id,foo,name,bar"}, }, { @@ -340,11 +381,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: objectSchema}, path: "/;id=foo;name=bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "matrix explode invalid", param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: objectSchema}, path: "/id=foo;name=bar", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "id=foo;name=bar"}, }, { @@ -352,23 +395,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectSchema}, path: "/id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "invalid integer prop", param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", integerSchema)}, path: "/foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid number prop", param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", numberSchema)}, path: "/foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid boolean prop", param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", booleanSchema)}, path: "/foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, }, @@ -381,35 +428,41 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: stringSchema}, query: "param=foo", want: "foo", + found: true, }, { name: "form explode", param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: stringSchema}, query: "param=foo", want: "foo", + found: true, }, { name: "default", param: &openapi3.Parameter{Name: "param", In: "query", Schema: stringSchema}, query: "param=foo", want: "foo", + found: true, }, { name: "string", param: &openapi3.Parameter{Name: "param", In: "query", Schema: stringSchema}, query: "param=foo", want: "foo", + found: true, }, { name: "integer", param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema}, query: "param=1", want: float64(1), + found: true, }, { name: "integer invalid", param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema}, query: "param=foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -417,11 +470,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema}, query: "param=1.1", want: 1.1, + found: true, }, { name: "number invalid", param: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema}, query: "param=foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -429,11 +484,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema}, query: "param=true", want: true, + found: true, }, { name: "boolean invalid", param: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema}, query: "param=foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, }, @@ -446,11 +503,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Schema: allofSchema}, query: "param=1", want: float64(1), + found: true, }, { name: "allofSchema string", param: &openapi3.Parameter{Name: "param", In: "query", Schema: allofSchema}, query: "param=abdf", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "abdf"}, }, }, @@ -463,12 +522,14 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Schema: anyofSchema}, query: "param=1", want: float64(1), + found: true, }, { name: "anyofSchema string", param: &openapi3.Parameter{Name: "param", In: "query", Schema: anyofSchema}, query: "param=abdf", want: "abdf", + found: true, }, }, }, @@ -480,18 +541,21 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema}, query: "param=true", want: true, + found: true, }, { name: "oneofSchema int", param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema}, query: "param=1122", want: float64(1122), + found: true, }, { name: "oneofSchema string", param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema}, query: "param=abcd", want: nil, + found: true, }, }, }, @@ -503,59 +567,69 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: arraySchema}, query: "param=foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "form explode", param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: arraySchema}, query: "param=foo¶m=bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "spaceDelimited", param: &openapi3.Parameter{Name: "param", In: "query", Style: "spaceDelimited", Explode: noExplode, Schema: arraySchema}, query: "param=foo bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "spaceDelimited explode", param: &openapi3.Parameter{Name: "param", In: "query", Style: "spaceDelimited", Explode: explode, Schema: arraySchema}, query: "param=foo¶m=bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "pipeDelimited", param: &openapi3.Parameter{Name: "param", In: "query", Style: "pipeDelimited", Explode: noExplode, Schema: arraySchema}, query: "param=foo|bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "pipeDelimited explode", param: &openapi3.Parameter{Name: "param", In: "query", Style: "pipeDelimited", Explode: explode, Schema: arraySchema}, query: "param=foo¶m=bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "default", param: &openapi3.Parameter{Name: "param", In: "query", Schema: arraySchema}, query: "param=foo¶m=bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "invalid integer items", param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(integerSchema)}, query: "param=1¶m=foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid number items", param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(numberSchema)}, query: "param=1.1¶m=foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid boolean items", param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(booleanSchema)}, query: "param=true¶m=foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, }, @@ -568,41 +642,48 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: objectSchema}, query: "param=id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "form explode", param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: objectSchema}, query: "id=foo&name=bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "deepObject explode", param: &openapi3.Parameter{Name: "param", In: "query", Style: "deepObject", Explode: explode, Schema: objectSchema}, query: "param[id]=foo¶m[name]=bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "default", param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectSchema}, query: "id=foo&name=bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "invalid integer prop", param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", integerSchema)}, query: "foo=bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid number prop", param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", numberSchema)}, query: "foo=bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid boolean prop", param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", booleanSchema)}, query: "foo=bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, }, @@ -615,35 +696,41 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: stringSchema}, header: "X-Param:foo", want: "foo", + found: true, }, { name: "simple explode", param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: explode, Schema: stringSchema}, header: "X-Param:foo", want: "foo", + found: true, }, { name: "default", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: stringSchema}, header: "X-Param:foo", want: "foo", + found: true, }, { name: "string", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: stringSchema}, header: "X-Param:foo", want: "foo", + found: true, }, { name: "integer", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema}, header: "X-Param:1", want: float64(1), + found: true, }, { name: "integer invalid", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema}, header: "X-Param:foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -651,11 +738,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema}, header: "X-Param:1.1", want: 1.1, + found: true, }, { name: "number invalid", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema}, header: "X-Param:foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -663,11 +752,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema}, header: "X-Param:true", want: true, + found: true, }, { name: "boolean invalid", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema}, header: "X-Param:foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, }, @@ -680,35 +771,41 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: arraySchema}, header: "X-Param:foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "simple explode", param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: explode, Schema: arraySchema}, header: "X-Param:foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "default", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arraySchema}, header: "X-Param:foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "invalid integer items", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(integerSchema)}, header: "X-Param:1,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid number items", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(numberSchema)}, header: "X-Param:1.1,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid boolean items", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(booleanSchema)}, header: "X-Param:true,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, }, @@ -721,35 +818,41 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: objectSchema}, header: "X-Param:id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "simple explode", param: &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: explode, Schema: objectSchema}, header: "X-Param:id=foo,name=bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "default", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectSchema}, header: "X-Param:id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "invalid integer prop", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", integerSchema)}, header: "X-Param:foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid number prop", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", numberSchema)}, header: "X-Param:foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid boolean prop", param: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", booleanSchema)}, header: "X-Param:foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, }, @@ -762,35 +865,41 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: stringSchema}, cookie: "X-Param:foo", want: "foo", + found: true, }, { name: "form explode", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: explode, Schema: stringSchema}, cookie: "X-Param:foo", want: "foo", + found: true, }, { name: "default", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: stringSchema}, cookie: "X-Param:foo", want: "foo", + found: true, }, { name: "string", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: stringSchema}, cookie: "X-Param:foo", want: "foo", + found: true, }, { name: "integer", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema}, cookie: "X-Param:1", want: float64(1), + found: true, }, { name: "integer invalid", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema}, cookie: "X-Param:foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -798,11 +907,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema}, cookie: "X-Param:1.1", want: 1.1, + found: true, }, { name: "number invalid", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema}, cookie: "X-Param:foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, { @@ -810,11 +921,13 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema}, cookie: "X-Param:true", want: true, + found: true, }, { name: "boolean invalid", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema}, cookie: "X-Param:foo", + found: true, err: &ParseError{Kind: KindInvalidFormat, Value: "foo"}, }, }, @@ -827,23 +940,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arraySchema}, cookie: "X-Param:foo,bar", want: []interface{}{"foo", "bar"}, + found: true, }, { name: "invalid integer items", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(integerSchema)}, cookie: "X-Param:1,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid number items", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(numberSchema)}, cookie: "X-Param:1.1,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, { name: "invalid boolean items", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(booleanSchema)}, cookie: "X-Param:true,foo", + found: true, err: &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}}, }, }, @@ -856,23 +973,27 @@ func TestDecodeParameter(t *testing.T) { param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectSchema}, cookie: "X-Param:id,foo,name,bar", want: map[string]interface{}{"id": "foo", "name": "bar"}, + found: true, }, { name: "invalid integer prop", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", integerSchema)}, cookie: "X-Param:foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid number prop", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", numberSchema)}, cookie: "X-Param:foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, { name: "invalid boolean prop", param: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", booleanSchema)}, cookie: "X-Param:foo,bar", + found: true, err: &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}}, }, }, @@ -931,7 +1052,9 @@ func TestDecodeParameter(t *testing.T) { require.NoError(t, err) input := &RequestValidationInput{Request: req, PathParams: pathParams, Route: route} - got, err := decodeStyledParameter(tc.param, input) + got, found, err := decodeStyledParameter(tc.param, input) + + require.Truef(t, found == tc.found, "got found: %t, want found: %t", found, tc.found) if tc.err != nil { require.Error(t, err) diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go index b1bb84fb1..fae6b09f9 100644 --- a/openapi3filter/validate_request.go +++ b/openapi3filter/validate_request.go @@ -19,6 +19,9 @@ var ErrAuthenticationServiceMissing = errors.New("missing AuthenticationFunc") // ErrInvalidRequired is returned when a required value of a parameter or request body is not defined. var ErrInvalidRequired = errors.New("value is required but missing") +// ErrInvalidEmptyValue is returned when a value of a parameter or request body is empty while it's not allowed. +var ErrInvalidEmptyValue = errors.New("empty value is not allowed") + // ValidateRequest is used to validate the given input according to previous // loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a // non-nil error will be returned. @@ -108,6 +111,7 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) error { // ValidateParameter validates a parameter's value by JSON schema. // The function returns RequestError with a ParseError cause when unable to parse a value. // The function returns RequestError with ErrInvalidRequired cause when a value of a required parameter is not defined. +// The function returns RequestError with ErrInvalidEmptyValue cause when a value of a required parameter is not defined. // The function returns RequestError with a openapi3.SchemaError cause when a value is invalid by JSON schema. func ValidateParameter(ctx context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error { if parameter.Schema == nil && parameter.Content == nil { @@ -124,23 +128,28 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param var value interface{} var err error + var found bool var schema *openapi3.Schema // Validation will ensure that we either have content or schema. if parameter.Content != nil { - if value, schema, err = decodeContentParameter(parameter, input); err != nil { + if value, schema, found, err = decodeContentParameter(parameter, input); err != nil { return &RequestError{Input: input, Parameter: parameter, Err: err} } } else { - if value, err = decodeStyledParameter(parameter, input); err != nil { + if value, found, err = decodeStyledParameter(parameter, input); err != nil { return &RequestError{Input: input, Parameter: parameter, Err: err} } schema = parameter.Schema.Value } - // Validate a parameter's value. - if value == nil { - if parameter.Required { - return &RequestError{Input: input, Parameter: parameter, Err: ErrInvalidRequired} + // Validate a parameter's value and presence. + if parameter.Required && !found { + return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidRequired.Error(), Err: ErrInvalidRequired} + } + + if isNilValue(value) { + if !parameter.AllowEmptyValue && found { + return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidEmptyValue.Error(), Err: ErrInvalidEmptyValue} } return nil } diff --git a/openapi3filter/validation_error_encoder.go b/openapi3filter/validation_error_encoder.go index 205186960..779887db0 100644 --- a/openapi3filter/validation_error_encoder.go +++ b/openapi3filter/validation_error_encoder.go @@ -34,6 +34,8 @@ func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http cErr = convertBasicRequestError(e) } else if e.Err == ErrInvalidRequired { cErr = convertErrInvalidRequired(e) + } else if e.Err == ErrInvalidEmptyValue { + cErr = convertErrInvalidEmptyValue(e) } else if innerErr, ok := e.Err.(*ParseError); ok { cErr = convertParseError(e, innerErr) } else if innerErr, ok := e.Err.(*openapi3.SchemaError); ok { @@ -87,6 +89,19 @@ func convertErrInvalidRequired(e *RequestError) *ValidationError { } } +func convertErrInvalidEmptyValue(e *RequestError) *ValidationError { + if e.Err == ErrInvalidEmptyValue && e.Parameter != nil { + return &ValidationError{ + Status: http.StatusBadRequest, + Title: fmt.Sprintf("parameter %q in %s is not allowed to be empty", e.Parameter.Name, e.Parameter.In), + } + } + return &ValidationError{ + Status: http.StatusBadRequest, + Title: e.Error(), + } +} + func convertParseError(e *RequestError, innerErr *ParseError) *ValidationError { // We treat path params of the wrong type like a 404 instead of a 400 if innerErr.Kind == KindInvalidFormat && e.Parameter != nil && e.Parameter.In == "path" { diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go index bdf544210..11032c141 100644 --- a/openapi3filter/validation_error_test.go +++ b/openapi3filter/validation_error_test.go @@ -181,7 +181,8 @@ func getValidationTests(t *testing.T) []*validationTest { }, wantErrParam: "status", wantErrParamIn: "query", - wantErrBody: `parameter "status" in query has an error: value is required but missing`, + wantErrBody: `parameter "status" in query has an error: value is required but missing: value is required but missing`, + wantErrReason: "value is required but missing", wantErrResponse: &ValidationError{Status: http.StatusBadRequest, Title: `parameter "status" in query is required`}, }, @@ -203,7 +204,25 @@ func getValidationTests(t *testing.T) []*validationTest { { name: "success - ignores unknown query string parameter", args: validationArgs{ - r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?wat=isdis", nil), + r: newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=available&wat=isdis", nil), + }, + }, + { + name: "error - non required query string has empty value", + args: validationArgs{ + r: newPetstoreRequest(t, http.MethodGet, "/pets/?tags=", nil), + }, + wantErrParam: "tags", + wantErrParamIn: "query", + wantErrBody: `parameter "tags" in query has an error: empty value is not allowed: empty value is not allowed`, + wantErrReason: "empty value is not allowed", + wantErrResponse: &ValidationError{Status: http.StatusBadRequest, + Title: `parameter "tags" in query is not allowed to be empty`}, + }, + { + name: "success - non required query string has empty value, but has AllowEmptyValue", + args: validationArgs{ + r: newPetstoreRequest(t, http.MethodGet, "/pets/?status=", nil), }, }, { @@ -425,7 +444,8 @@ func getValidationTests(t *testing.T) []*validationTest { }, wantErrParam: "petId", wantErrParamIn: "path", - wantErrBody: `parameter "petId" in path has an error: value is required but missing`, + wantErrBody: `parameter "petId" in path has an error: value is required but missing: value is required but missing`, + wantErrReason: "value is required but missing", wantErrResponse: &ValidationError{Status: http.StatusBadRequest, Title: `parameter "petId" in path is required`}, },