Repository: getkin/kin-openapi
Pull request #72 (2019-03-13 12:18:22)

Add support for keyword "explode" in an operation's parameter
Proposed fix for issue #68.

I also implemented reading of a parameter's value according to a serialization method as it defined is the specification [(Describing parameters](https://swagger.io/docs/specification/describing-parameters/) and [Parameter serialization](https://swagger.io/docs/specification/serialization/))

nikolaas: @fenollp I fixed all your comments.
nikolaas: @fenollp I done it.

Patch:
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 5d6d6f3..8a469d0 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -55,6 +55,7 @@ type Parameter struct {
 	In              string                 `json:"in,omitempty"`
 	Description     string                 `json:"description,omitempty"`
 	Style           string                 `json:"style,omitempty"`
+	Explode         *bool                  `json:"explode,omitempty"`
 	AllowEmptyValue bool                   `json:"allowEmptyValue,omitempty"`
 	AllowReserved   bool                   `json:"allowReserved,omitempty"`
 	Deprecated      bool                   `json:"deprecated,omitempty"`
@@ -72,6 +73,16 @@ const (
 	ParameterInCookie = "cookie"
 )
 
+const (
+	SerializationSimple         = "simple"
+	SerializationLabel          = "label"
+	SerializationMatrix         = "matrix"
+	SerializationForm           = "form"
+	SerializationSpaceDelimited = "spaceDelimited"
+	SerializationPipeDelimited  = "pipeDelimited"
+	SerializationDeepObject     = "deepObject"
+)
+
 func NewPathParameter(name string) *Parameter {
 	return &Parameter{
 		Name:     name,
@@ -130,6 +141,41 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, parameter)
 }
 
+type SerializationMethod struct {
+	Style   string
+	Explode bool
+}
+
+// SerializationMethod returns a parameter's serialization method.
+// When a parameter's serialization method is not defined the method returns
+// the default serialization method corresponding to a parameter's location.
+func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) {
+	switch parameter.In {
+	case ParameterInPath, ParameterInHeader:
+		style := parameter.Style
+		if style == "" {
+			style = SerializationSimple
+		}
+		explode := false
+		if parameter.Explode != nil {
+			explode = *parameter.Explode
+		}
+		return &SerializationMethod{Style: style, Explode: explode}, nil
+	case ParameterInQuery, ParameterInCookie:
+		style := parameter.Style
+		if style == "" {
+			style = SerializationForm
+		}
+		explode := true
+		if parameter.Explode != nil {
+			explode = *parameter.Explode
+		}
+		return &SerializationMethod{Style: style, Explode: explode}, nil
+	default:
+		return nil, fmt.Errorf("unexpected parameter's 'in': %q", parameter.In)
+	}
+}
+
 func (parameter *Parameter) Validate(c context.Context) error {
 	if parameter.Name == "" {
 		return errors.New("Parameter name can't be blank")
@@ -144,6 +190,41 @@ func (parameter *Parameter) Validate(c context.Context) error {
 	default:
 		return fmt.Errorf("Parameter can't have 'in' value '%s'", parameter.In)
 	}
+
+	// Validate a parameter's serialization method.
+	sm, err := parameter.SerializationMethod()
+	if err != nil {
+		return err
+	}
+	var smSupported bool
+	switch {
+	case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
+
+		parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
+
+		parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
+		parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
+
+		parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
+		parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
+		smSupported = true
+	}
+	if !smSupported {
+		return fmt.Errorf("Parameter '%v' schema is invalid: %v", parameter.Name,
+			fmt.Errorf("Serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in))
+	}
+
 	if parameter.Schema != nil && parameter.Content != nil {
 		return fmt.Errorf("Parameter '%v' schema is invalid: %v", parameter.Name,
 			errors.New("Cannot contain both schema and content in a parameter"))
diff --git a/openapi3filter/param_decoder.go b/openapi3filter/param_decoder.go
new file mode 100644
index 0000000..41b424b
--- /dev/null
+++ b/openapi3filter/param_decoder.go
@@ -0,0 +1,626 @@
+package openapi3filter
+
+import (
+	"fmt"
+	"net/http"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+const (
+	errMsgInvalidValue          = "an invalid value"
+	errMsgInvalidSerializationF = "%s parameter %q has an invalid serialization method: style=%q, explode=%v"
+)
+
+// ParseError describes errors which happens while parse operation's parameters.
+type ParseError struct {
+	Value  interface{}
+	Path   []interface{}
+	Reason string
+	Cause  error
+}
+
+func (e *ParseError) Error() string {
+	var msg []string
+	if e.Path != nil {
+		msg = append(msg, fmt.Sprintf("failed to parse value %v at path %v", e.Value, e.Path))
+	} else {
+		msg = append(msg, fmt.Sprintf("failed to parse value %v", e.Value))
+	}
+	if e.Reason != "" {
+		msg = append(msg, e.Reason)
+	}
+	if e.Cause != nil {
+		msg = append(msg, e.Cause.Error())
+	}
+	return strings.Join(msg, ": ")
+}
+
+// decodeParameter returns a value of an operation's parameter from HTTP request.
+func decodeParameter(param *openapi3.Parameter, input *RequestValidationInput) (interface{}, error) {
+	var decoder interface {
+		DecodePrimitive(param *openapi3.Parameter) (interface{}, error)
+		DecodeArray(param *openapi3.Parameter) ([]interface{}, error)
+		DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error)
+	}
+
+	switch param.In {
+	case openapi3.ParameterInPath:
+		decoder = &pathParamDecoder{input: input}
+	case openapi3.ParameterInQuery:
+		decoder = &queryParamDecoder{input: input}
+	case openapi3.ParameterInHeader:
+		decoder = &headerParamDecoder{input: input}
+	case openapi3.ParameterInCookie:
+		decoder = &cookieParamDecoder{input: input}
+	default:
+		return nil, fmt.Errorf("unsupported parameter's 'in': %s", param.In)
+	}
+
+	switch param.Schema.Value.Type {
+	case "array":
+		return decoder.DecodeArray(param)
+	case "object":
+		return decoder.DecodeObject(param)
+	default:
+		return decoder.DecodePrimitive(param)
+	}
+}
+
+// pathParamDecoder decodes values of path parameters.
+type pathParamDecoder struct {
+	input *RequestValidationInput
+}
+
+func (d *pathParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	var prefix string
+	switch sm.Style {
+	case "simple":
+		// A prefix is empty for style "simple".
+	case "label":
+		prefix = "."
+	case "matrix":
+		prefix = ";" + param.Name + "="
+	default:
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	if d.input.PathParams == nil {
+		// HTTP request does not contains a value of the target path parameter.
+		return nil, nil
+	}
+	raw, ok := d.input.PathParams[d.paramKey(param, sm)]
+	if !ok || raw == "" {
+		// HTTP request does not contains a value of the target path parameter.
+		return nil, nil
+	}
+	src, err := cutPrefix(raw, prefix)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	val, err := parsePrimitive(src, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *pathParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	var prefix, delim string
+	switch {
+	case sm.Style == "simple":
+		delim = ","
+	case sm.Style == "label" && sm.Explode == false:
+		prefix = "."
+		delim = ","
+	case sm.Style == "label" && sm.Explode == true:
+		prefix = "."
+		delim = "."
+	case sm.Style == "matrix" && sm.Explode == false:
+		prefix = ";" + param.Name + "="
+		delim = ","
+	case sm.Style == "matrix" && sm.Explode == true:
+		prefix = ";" + param.Name + "="
+		delim = ";" + param.Name + "="
+	default:
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	if d.input.PathParams == nil {
+		// HTTP request does not contains a value of the target path parameter.
+		return nil, nil
+	}
+	raw, ok := d.input.PathParams[d.paramKey(param, sm)]
+	if !ok || raw == "" {
+		// HTTP request does not contains a value of the target path parameter.
+		return nil, nil
+	}
+	src, err := cutPrefix(raw, prefix)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	val, err := parseArray(strings.Split(src, delim), param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *pathParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	var prefix, propsDelim, valueDelim string
+	switch {
+	case sm.Style == "simple" && sm.Explode == false:
+		propsDelim = ","
+		valueDelim = ","
+	case sm.Style == "simple" && sm.Explode == true:
+		propsDelim = ","
+		valueDelim = "="
+	case sm.Style == "label" && sm.Explode == false:
+		prefix = "."
+		propsDelim = ","
+		valueDelim = ","
+	case sm.Style == "label" && sm.Explode == true:
+		prefix = "."
+		propsDelim = "."
+		valueDelim = "="
+	case sm.Style == "matrix" && sm.Explode == false:
+		prefix = ";" + param.Name + "="
+		propsDelim = ","
+		valueDelim = ","
+	case sm.Style == "matrix" && sm.Explode == true:
+		prefix = ";"
+		propsDelim = ";"
+		valueDelim = "="
+	default:
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	if d.input.PathParams == nil {
+		// HTTP request does not contains a value of the target path parameter.
+		return nil, nil
+	}
+	raw, ok := d.input.PathParams[d.paramKey(param, sm)]
+	if !ok || raw == "" {
+		// HTTP request does not contains a value of the target path parameter.
+		return nil, nil
+	}
+	src, err := cutPrefix(raw, prefix)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	props, err := propsFromString(src, propsDelim, valueDelim)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	val, err := makeObject(props, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+// paramKey returns a key to get a raw value of a path parameter.
+func (d *pathParamDecoder) paramKey(param *openapi3.Parameter, sm *openapi3.SerializationMethod) string {
+	switch sm.Style {
+	case "label":
+		return "." + param.Name
+	case "matrix":
+		return ";" + param.Name
+	default:
+		return param.Name
+	}
+}
+
+// cutPrefix validates that a raw value of a path parameter has the specified prefix,
+// and returns a raw value without the prefix.
+func cutPrefix(raw, prefix string) (string, error) {
+	if prefix == "" {
+		return raw, nil
+	}
+	if len(raw) < len(prefix) || raw[:len(prefix)] != prefix {
+		return "", &ParseError{Value: raw, Reason: fmt.Sprintf("a value must be prefixed with %q", prefix)}
+	}
+	return raw[len(prefix):], nil
+}
+
+// queryParamDecoder decodes values of query parameters.
+type queryParamDecoder struct {
+	input *RequestValidationInput
+}
+
+func (d *queryParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style != "form" {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	values := d.input.GetQueryParams()[param.Name]
+	if len(values) == 0 {
+		// HTTP request does not contain a value of the target query parameter.
+		return nil, nil
+	}
+	val, err := parsePrimitive(values[0], param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *queryParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style == "deepObject" {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	values := d.input.GetQueryParams()[param.Name]
+	if len(values) == 0 {
+		// HTTP request does not contain a value of the target query parameter.
+		return nil, nil
+	}
+	if !sm.Explode {
+		var delim string
+		switch sm.Style {
+		case "form":
+			delim = ","
+		case "spaceDelimited":
+			delim = " "
+		case "pipeDelimited":
+			delim = "|"
+		}
+		values = strings.Split(values[0], delim)
+	}
+	val, err := parseArray(values, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *queryParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error) {
+	var propsFn func(map[string][]string) (map[string]string, error)
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	switch sm.Style {
+	case "form":
+		propsFn = func(params map[string][]string) (map[string]string, error) {
+			if len(params) == 0 {
+				// HTTP request does not contain query parameters.
+				return nil, nil
+			}
+			if sm.Explode {
+				props := make(map[string]string)
+				for key, values := range params {
+					props[key] = values[0]
+				}
+				return props, nil
+			}
+			values := params[param.Name]
+			if len(values) == 0 {
+				// HTTP request does not contain a value of the target query parameter.
+				return nil, nil
+			}
+			return propsFromString(values[0], ",", ",")
+		}
+	case "deepObject":
+		propsFn = func(params map[string][]string) (map[string]string, error) {
+			props := make(map[string]string)
+			for key, values := range params {
+				groups := regexp.MustCompile(fmt.Sprintf("%s\\[(.+?)\\]", param.Name)).FindAllStringSubmatch(key, -1)
+				if len(groups) == 0 {
+					// A query parameter's name does not match the required format, so skip it.
+					continue
+				}
+				props[groups[0][1]] = values[0]
+			}
+			if len(props) == 0 {
+				// HTTP request does not contain query parameters encoded by rules of style "deepObject".
+				return nil, nil
+			}
+			return props, nil
+		}
+	default:
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	props, err := propsFn(d.input.GetQueryParams())
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	if props == nil {
+		return nil, nil
+	}
+	val, err := makeObject(props, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+// headerParamDecoder decodes values of header parameters.
+type headerParamDecoder struct {
+	input *RequestValidationInput
+}
+
+func (d *headerParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style != "simple" {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	raw := d.input.Request.Header.Get(http.CanonicalHeaderKey(param.Name))
+	val, err := parsePrimitive(raw, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *headerParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style != "simple" {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	raw := d.input.Request.Header.Get(http.CanonicalHeaderKey(param.Name))
+
+	val, err := parseArray(strings.Split(raw, ","), param.Schema)
+	if raw == "" {
+		// HTTP request does not contains a corresponding header
+		return nil, nil
+	}
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *headerParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style != "simple" {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+	valueDelim := ","
+	if sm.Explode {
+		valueDelim = "="
+	}
+
+	raw := d.input.Request.Header.Get(http.CanonicalHeaderKey(param.Name))
+	if raw == "" {
+		// HTTP request does not contain a corresponding header.
+		return nil, nil
+	}
+	props, err := propsFromString(raw, ",", valueDelim)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	val, err := makeObject(props, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+// cookieParamDecoder decodes values of cookie parameters.
+type cookieParamDecoder struct {
+	input *RequestValidationInput
+}
+
+func (d *cookieParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style != "form" {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	cookie, err := d.input.Request.Cookie(param.Name)
+	if err == http.ErrNoCookie {
+		// HTTP request does not contain a corresponding cookie.
+		return nil, nil
+	}
+	if err != nil {
+		return nil, fmt.Errorf("decode param %q: %s", param.Name, err)
+	}
+	val, err := parsePrimitive(cookie.Value, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *cookieParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style != "form" || sm.Explode {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	cookie, err := d.input.Request.Cookie(param.Name)
+	if err == http.ErrNoCookie {
+		// HTTP request does not contain a corresponding cookie.
+		return nil, nil
+	}
+	if err != nil {
+		return nil, fmt.Errorf("decode param %q: %s", param.Name, err)
+	}
+	val, err := parseArray(strings.Split(cookie.Value, ","), param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+func (d *cookieParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error) {
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
+	}
+	if sm.Style != "form" || sm.Explode {
+		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+	}
+
+	cookie, err := d.input.Request.Cookie(param.Name)
+	if err == http.ErrNoCookie {
+		// HTTP request does not contain a corresponding cookie.
+		return nil, nil
+	}
+	if err != nil {
+		return nil, fmt.Errorf("decode param %q: %s", param.Name, err)
+	}
+	props, err := propsFromString(cookie.Value, ",", ",")
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	val, err := makeObject(props, param.Schema)
+	if err != nil {
+		return nil, &RequestError{Input: d.input, Parameter: param, Reason: errMsgInvalidValue, Err: err}
+	}
+	return val, nil
+}
+
+// propsFromString returns a properties map that is created by splitting a source string by propDelim and valueDelim.
+// The source string must have a valid format: pairs <propName><valueDelim><propValue> separated by <propDelim>.
+// The function returns an error when the source string has an invalid format.
+func propsFromString(src, propDelim, valueDelim string) (map[string]string, error) {
+	props := make(map[string]string)
+	pairs := strings.Split(src, propDelim)
+
+	// When propDelim and valueDelim is equal the source string follow the next rule:
+	// every even item of pairs is a properies's name, and the subsequent odd item is a property's value.
+	if propDelim == valueDelim {
+		// Taking into account the rule above, a valid source string must be splitted by propDelim
+		// to an array with an even number of items.
+		if len(pairs)%2 != 0 {
+			return nil, &ParseError{
+				Value:  src,
+				Reason: fmt.Sprintf("a value must be a list of object's properties in format \"name%svalue\" separated by %s", valueDelim, propDelim),
+			}
+		}
+		for i := 0; i < len(pairs)/2; i++ {
+			props[pairs[i*2]] = pairs[i*2+1]
+		}
+		return props, nil
+	}
+
+	// When propDelim and valueDelim is not equal the source string follow the next rule:
+	// every item of pairs is a string that follows format <propName><valueDelim><propValue>.
+	for _, pair := range pairs {
+		prop := strings.Split(pair, valueDelim)
+		if len(prop) != 2 {
+			return nil, &ParseError{
+				Value:  src,
+				Reason: fmt.Sprintf("a value must be a list of object's properties in format \"name%svalue\" separated by %s", valueDelim, propDelim),
+			}
+		}
+		props[prop[0]] = prop[1]
+	}
+	return props, nil
+}
+
+// makeObject returns an object that contains properties from props.
+// A value of every property is parsed as a primitive value.
+// The function returns an error when an error happened while parse object's properties.
+func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
+	obj := make(map[string]interface{})
+	for propName, propSchema := range schema.Value.Properties {
+		value, err := parsePrimitive(props[propName], propSchema)
+		if err != nil {
+			if v, ok := err.(*ParseError); ok {
+				return nil, &ParseError{
+					Value:  v.Value,
+					Reason: v.Reason,
+					Cause:  v.Cause,
+					Path:   []interface{}{propName},
+				}
+			}
+			return nil, err
+		}
+		obj[propName] = value
+	}
+	return obj, nil
+}
+
+// parseArray returns an array that contains items from a raw array.
+// Every item is parsed as a primitive value.
+// The function returns an error when an error happened while parse array's items.
+func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, error) {
+	var value []interface{}
+	for i, v := range raw {
+		item, err := parsePrimitive(v, schemaRef.Value.Items)
+		if err != nil {
+			if v, ok := err.(*ParseError); ok {
+				return nil, &ParseError{
+					Value:  v.Value,
+					Reason: v.Reason,
+					Cause:  v.Cause,
+					Path:   []interface{}{i},
+				}
+			}
+			return nil, err
+		}
+		value = append(value, item)
+	}
+	return value, nil
+}
+
+// parsePrimitive returns a value that is created by parsing a source string to a primitive type
+// that is specified by a JSON schema. The function returns nil when the source string is empty.
+// The function panics when a JSON schema has a non primitive type.
+func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error) {
+	if raw == "" {
+		return nil, nil
+	}
+	switch schema.Value.Type {
+	case "integer", "number":
+		v, err := strconv.ParseFloat(raw, 64)
+		if err != nil {
+			return nil, &ParseError{Value: raw, Cause: err}
+		}
+		return v, nil
+	case "boolean":
+		v, err := strconv.ParseBool(raw)
+		if err != nil {
+			return nil, &ParseError{Value: raw, Cause: err}
+		}
+		return v, nil
+	case "string":
+		return raw, nil
+	default:
+		panic(fmt.Sprintf("schema has non primitive type %q", schema.Value.Type))
+	}
+}
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index d03345c..723b297 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -7,7 +7,6 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
-	"net/http"
 	"sort"
 
 	"github.com/getkin/kin-openapi/openapi3"
@@ -68,51 +67,13 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error {
 }
 
 func ValidateParameter(c context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error {
-	req := input.Request
-	name := parameter.Name
-	var value string
-	var found bool
-	switch parameter.In {
-	case openapi3.ParameterInPath:
-		pathParams := input.PathParams
-		if pathParams != nil {
-			value, found = pathParams[name]
-		}
-	case openapi3.ParameterInQuery:
-		values := input.GetQueryParams()[name]
-		if len(values) > 0 {
-			value = values[0]
-			found = true
-		}
-	case openapi3.ParameterInHeader:
-		var values []string
-		values, found = req.Header[http.CanonicalHeaderKey(name)]
-		if len(values) > 0 {
-			value = values[0]
-		}
-	case openapi3.ParameterInCookie:
-		cookie, err := req.Cookie(name)
-		if err == nil {
-			value = cookie.Value
-			found = true
-		} else {
-			if err != http.ErrNoCookie {
-				return &RequestError{
-					Input:     input,
-					Parameter: parameter,
-					Reason:    "parsing failed",
-					Err:       err,
-				}
-			}
-		}
-	default:
-		return &RequestError{
-			Input:     input,
-			Parameter: parameter,
-			Reason:    "unsupported 'in'",
-		}
+	value, err := decodeParameter(parameter, input)
+	if err != nil {
+		return err
 	}
-	if !found {
+
+	// Validate a parameter's value.
+	if value == nil {
 		if parameter.Required {
 			return &RequestError{
 				Input:     input,
@@ -125,12 +86,8 @@ func ValidateParameter(c context.Context, input *RequestValidationInput, paramet
 	if schemaRef := parameter.Schema; schemaRef != nil {
 		// Only check schema if no transformation is needed
 		if schema := schemaRef.Value; schema.Type == "string" {
-			if err := schema.VisitJSONString(value); err != nil {
-				return &RequestError{
-					Input:     input,
-					Parameter: parameter,
-					Err:       err,
-				}
+			if err = schema.VisitJSON(value); err != nil {
+				return &RequestError{Input: input, Parameter: parameter, Err: err}
 			}
 		}
 	}


Test patch:
diff --git a/openapi3filter/param_decoder_test.go b/openapi3filter/param_decoder_test.go
new file mode 100644
index 0000000..55d86ff
--- /dev/null
+++ b/openapi3filter/param_decoder_test.go
@@ -0,0 +1,1029 @@
+package openapi3filter
+
+import (
+	"net/http"
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/stretchr/testify/require"
+)
+
+func TestDecodeParameter(t *testing.T) {
+	var (
+		boolPtr   = func(b bool) *bool { return &b }
+		explode   = boolPtr(true)
+		noExplode = boolPtr(false)
+		arrayOf   = func(items *openapi3.SchemaRef) *openapi3.SchemaRef {
+			return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "array", Items: items}}
+		}
+		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")
+			}
+			for i := 0; i < len(args)/2; i++ {
+				propName := args[i*2].(string)
+				propSchema := args[i*2+1].(*openapi3.SchemaRef)
+				s.Value.Properties[propName] = propSchema
+			}
+			return s
+		}
+
+		integerSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "integer"}}
+		numberSchema  = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "number"}}
+		booleanSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "boolean"}}
+		stringSchema  = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "string"}}
+		arraySchema   = arrayOf(stringSchema)
+		objectSchema  = objectOf("id", stringSchema, "name", stringSchema)
+	)
+
+	type testCase struct {
+		name   string
+		param  *openapi3.Parameter
+		path   string
+		query  string
+		header string
+		cookie string
+		want   interface{}
+		err    error
+	}
+
+	testGroups := []struct {
+		name      string
+		testCases []testCase
+	}{
+		{
+			name: "path primitive",
+			testCases: []testCase{
+				{
+					name:  "simple",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: stringSchema},
+					path:  "/foo",
+					want:  "foo",
+				},
+				{
+					name:  "simple explode",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: stringSchema},
+					path:  "/foo",
+					want:  "foo",
+				},
+				{
+					name:  "label",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringSchema},
+					path:  "/.foo",
+					want:  "foo",
+				},
+				{
+					name:  "label invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringSchema},
+					path:  "/foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: stringSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "label explode",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringSchema},
+					path:  "/.foo",
+					want:  "foo",
+				},
+				{
+					name:  "label explode invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringSchema},
+					path:  "/foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: stringSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "matrix",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringSchema},
+					path:  "/;param=foo",
+					want:  "foo",
+				},
+				{
+					name:  "matrix invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringSchema},
+					path:  "/foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: stringSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "matrix explode",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringSchema},
+					path:  "/;param=foo",
+					want:  "foo",
+				},
+				{
+					name:  "matrix explode invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringSchema},
+					path:  "/foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: stringSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "default",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: stringSchema},
+					path:  "/foo",
+					want:  "foo",
+				},
+				{
+					name:  "string",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: stringSchema},
+					path:  "/foo",
+					want:  "foo",
+				},
+				{
+					name:  "integer",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema},
+					path:  "/1",
+					want:  float64(1),
+				},
+				{
+					name:  "integer invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema},
+					path:  "/foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "number",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema},
+					path:  "/1.1",
+					want:  1.1,
+				},
+				{
+					name:  "number invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema},
+					path:  "/foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "boolean",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema},
+					path:  "/true",
+					want:  true,
+				},
+				{
+					name:  "boolean invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema},
+					path:  "/foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "path array",
+			testCases: []testCase{
+				{
+					name:  "simple",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: noExplode, Schema: arraySchema},
+					path:  "/foo,bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "simple explode",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "simple", Explode: explode, Schema: arraySchema},
+					path:  "/foo,bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "label",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: arraySchema},
+					path:  "/.foo,bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "label invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: arraySchema},
+					path:  "/foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: arraySchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "label explode",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: arraySchema},
+					path:  "/.foo.bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "label explode invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: arraySchema},
+					path:  "/foo.bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: arraySchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "matrix",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: arraySchema},
+					path:  "/;param=foo,bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "matrix invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: arraySchema},
+					path:  "/foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: arraySchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "matrix explode",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: arraySchema},
+					path:  "/;param=foo;param=bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "matrix explode invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: arraySchema},
+					path:  "/foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: arraySchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "default",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: arraySchema},
+					path:  "/foo,bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "invalid integer items",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(integerSchema)},
+					path:  "/1,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid number items",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(numberSchema)},
+					path:  "/1.1,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid boolean items",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(booleanSchema)},
+					path:  "/true,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "path object",
+			testCases: []testCase{
+				{
+					name:  "simple",
+					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"},
+				},
+				{
+					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"},
+				},
+				{
+					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"},
+				},
+				{
+					name:  "label invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: objectSchema},
+					path:  "/id,foo,name,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: noExplode, Schema: objectSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "label explode",
+					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"},
+				},
+				{
+					name:  "label explode invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: objectSchema},
+					path:  "/id=foo.name=bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "label", Explode: explode, Schema: objectSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "matrix",
+					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"},
+				},
+				{
+					name:  "matrix invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: objectSchema},
+					path:  "/id,foo,name,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: noExplode, Schema: objectSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "matrix explode",
+					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"},
+				},
+				{
+					name:  "matrix explode invalid",
+					param: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: objectSchema},
+					path:  "/id=foo;name=bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Style: "matrix", Explode: explode, Schema: objectSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "default",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectSchema},
+					path:  "/id,foo,name,bar",
+					want:  map[string]interface{}{"id": "foo", "name": "bar"},
+				},
+				{
+					name:  "invalid integer prop",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", integerSchema)},
+					path:  "/foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid number prop",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", numberSchema)},
+					path:  "/foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid boolean prop",
+					param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", booleanSchema)},
+					path:  "/foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "query primitive",
+			testCases: []testCase{
+				{
+					name:  "form",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: stringSchema},
+					query: "param=foo",
+					want:  "foo",
+				},
+				{
+					name:  "form explode",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: stringSchema},
+					query: "param=foo",
+					want:  "foo",
+				},
+				{
+					name:  "default",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: stringSchema},
+					query: "param=foo",
+					want:  "foo",
+				},
+				{
+					name:  "string",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: stringSchema},
+					query: "param=foo",
+					want:  "foo",
+				},
+				{
+					name:  "integer",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema},
+					query: "param=1",
+					want:  float64(1),
+				},
+				{
+					name:  "integer invalid",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema},
+					query: "param=foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "number",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema},
+					query: "param=1.1",
+					want:  1.1,
+				},
+				{
+					name:  "number invalid",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema},
+					query: "param=foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "boolean",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema},
+					query: "param=true",
+					want:  true,
+				},
+				{
+					name:  "boolean invalid",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema},
+					query: "param=foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "query array",
+			testCases: []testCase{
+				{
+					name:  "form",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: noExplode, Schema: arraySchema},
+					query: "param=foo,bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "form explode",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "form", Explode: explode, Schema: arraySchema},
+					query: "param=foo&param=bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "spaceDelimited",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "spaceDelimited", Explode: noExplode, Schema: arraySchema},
+					query: "param=foo bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "spaceDelimited explode",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "spaceDelimited", Explode: explode, Schema: arraySchema},
+					query: "param=foo&param=bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "pipeDelimited",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "pipeDelimited", Explode: noExplode, Schema: arraySchema},
+					query: "param=foo|bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "pipeDelimited explode",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "pipeDelimited", Explode: explode, Schema: arraySchema},
+					query: "param=foo&param=bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "default",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: arraySchema},
+					query: "param=foo&param=bar",
+					want:  []interface{}{"foo", "bar"},
+				},
+				{
+					name:  "invalid integer items",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(integerSchema)},
+					query: "param=1&param=foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid number items",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(numberSchema)},
+					query: "param=1.1&param=foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid boolean items",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(booleanSchema)},
+					query: "param=true&param=foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "query object",
+			testCases: []testCase{
+				{
+					name:  "form",
+					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"},
+				},
+				{
+					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"},
+				},
+				{
+					name:  "deepObject explode",
+					param: &openapi3.Parameter{Name: "param", In: "query", Style: "deepObject", Explode: explode, Schema: objectSchema},
+					query: "param[id]=foo&param[name]=bar",
+					want:  map[string]interface{}{"id": "foo", "name": "bar"},
+				},
+				{
+					name:  "default",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectSchema},
+					query: "id=foo&name=bar",
+					want:  map[string]interface{}{"id": "foo", "name": "bar"},
+				},
+				{
+					name:  "invalid integer prop",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", integerSchema)},
+					query: "foo=bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid number prop",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", numberSchema)},
+					query: "foo=bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:  "invalid boolean prop",
+					param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", booleanSchema)},
+					query: "foo=bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "header primitive",
+			testCases: []testCase{
+				{
+					name:   "simple",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: stringSchema},
+					header: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "simple explode",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: explode, Schema: stringSchema},
+					header: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "default",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: stringSchema},
+					header: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "string",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: stringSchema},
+					header: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "integer",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
+					header: "X-Param:1",
+					want:   float64(1),
+				},
+				{
+					name:   "integer invalid",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
+					header: "X-Param:foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "number",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema},
+					header: "X-Param:1.1",
+					want:   1.1,
+				},
+				{
+					name:   "number invalid",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema},
+					header: "X-Param:foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "boolean",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema},
+					header: "X-Param:true",
+					want:   true,
+				},
+				{
+					name:   "boolean invalid",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema},
+					header: "X-Param:foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "header array",
+			testCases: []testCase{
+				{
+					name:   "simple",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Style: "simple", Explode: noExplode, Schema: arraySchema},
+					header: "X-Param:foo,bar",
+					want:   []interface{}{"foo", "bar"},
+				},
+				{
+					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"},
+				},
+				{
+					name:   "default",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arraySchema},
+					header: "X-Param:foo,bar",
+					want:   []interface{}{"foo", "bar"},
+				},
+				{
+					name:   "invalid integer items",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(integerSchema)},
+					header: "X-Param:1,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "invalid number items",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(numberSchema)},
+					header: "X-Param:1.1,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "invalid boolean items",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(booleanSchema)},
+					header: "X-Param:true,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "header object",
+			testCases: []testCase{
+				{
+					name:   "simple",
+					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"},
+				},
+				{
+					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"},
+				},
+				{
+					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"},
+				},
+				{
+					name:   "invalid integer prop",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", integerSchema)},
+					header: "X-Param:foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "invalid number prop",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", numberSchema)},
+					header: "X-Param:foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "invalid boolean prop",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", booleanSchema)},
+					header: "X-Param:foo,bar",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "cookie primitive",
+			testCases: []testCase{
+				{
+					name:   "form",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: stringSchema},
+					cookie: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "form explode",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: explode, Schema: stringSchema},
+					cookie: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "default",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: stringSchema},
+					cookie: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "string",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: stringSchema},
+					cookie: "X-Param:foo",
+					want:   "foo",
+				},
+				{
+					name:   "integer",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema},
+					cookie: "X-Param:1",
+					want:   float64(1),
+				},
+				{
+					name:   "integer invalid",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema},
+					cookie: "X-Param:foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "number",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema},
+					cookie: "X-Param:1.1",
+					want:   1.1,
+				},
+				{
+					name:   "number invalid",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema},
+					cookie: "X-Param:foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "boolean",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema},
+					cookie: "X-Param:true",
+					want:   true,
+				},
+				{
+					name:   "boolean invalid",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema},
+					cookie: "X-Param:foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "cookie array",
+			testCases: []testCase{
+				{
+					name:   "form",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arraySchema},
+					cookie: "X-Param:foo,bar",
+					want:   []interface{}{"foo", "bar"},
+				},
+				{
+					name:   "invalid integer items",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(integerSchema)},
+					cookie: "X-Param:1,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					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",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					name:   "invalid boolean items",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(booleanSchema)},
+					cookie: "X-Param:true,foo",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: arrayOf(booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+		{
+			name: "cookie object",
+			testCases: []testCase{
+				{
+					name:   "form",
+					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"},
+				},
+				{
+					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",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", integerSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					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",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", numberSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+				{
+					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",
+					err: &RequestError{
+						Parameter: &openapi3.Parameter{Name: "X-Param", In: "cookie", Style: "form", Explode: noExplode, Schema: objectOf("foo", booleanSchema)},
+						Reason:    "an invalid value",
+					},
+				},
+			},
+		},
+	}
+
+	for _, tg := range testGroups {
+		t.Run(tg.name, func(t *testing.T) {
+			for _, tc := range tg.testCases {
+				t.Run(tc.name, func(t *testing.T) {
+					req, err := http.NewRequest(http.MethodGet, "http://test.org/test"+tc.path, nil)
+					require.NoError(t, err, "failed to create a test request")
+
+					if tc.query != "" {
+						query := req.URL.Query()
+						for _, param := range strings.Split(tc.query, "&") {
+							v := strings.Split(param, "=")
+							query.Add(v[0], v[1])
+						}
+						req.URL.RawQuery = query.Encode()
+					}
+
+					if tc.header != "" {
+						v := strings.Split(tc.header, ":")
+						req.Header.Add(v[0], v[1])
+					}
+
+					if tc.cookie != "" {
+						v := strings.Split(tc.cookie, ":")
+						req.AddCookie(&http.Cookie{Name: v[0], Value: v[1]})
+					}
+
+					var path string
+					if tc.path != "" {
+						switch tc.param.Style {
+						case "label":
+							path = "." + tc.param.Name
+						case "matrix":
+							path = ";" + tc.param.Name
+						default:
+							path = tc.param.Name
+						}
+						if tc.param.Explode != nil && *tc.param.Explode {
+							path += "*"
+						}
+						path = "/{" + path + "}"
+					}
+
+					spec := &openapi3.Swagger{}
+					op := &openapi3.Operation{OperationID: "test", Parameters: []*openapi3.ParameterRef{{Value: tc.param}}}
+					spec.AddOperation("/test"+path, http.MethodGet, op)
+					router := NewRouter()
+					require.NoError(t, router.AddSwagger(spec), "failed to create a router")
+
+					route, pathParams, err := router.FindRoute(req.Method, req.URL)
+					require.NoError(t, err, "failed to find a route")
+
+					input := &RequestValidationInput{Request: req, PathParams: pathParams, Route: route}
+					got, err := decodeParameter(tc.param, input)
+
+					if tc.err != nil {
+						require.Error(t, err)
+						require.Truef(t, matchParamError(err, tc.err), "got error %v, want error %v", err, tc.err)
+						return
+					}
+
+					require.NoError(t, err)
+					require.Truef(t, reflect.DeepEqual(got, tc.want), "got %v, want %v", got, tc.want)
+				})
+			}
+		})
+	}
+}
+
+func matchParamError(got, want error) bool {
+	wErr, ok := want.(*RequestError)
+	if !ok {
+		return false
+	}
+	gErr, ok := got.(*RequestError)
+	if !ok {
+		return false
+	}
+
+	if !reflect.DeepEqual(wErr.Parameter, gErr.Parameter) {
+		return false
+	}
+	if wErr.Status != gErr.Status {
+		return false
+	}
+	if wErr.Reason != gErr.Reason {
+		return false
+	}
+	return true
+}