Repository: getkin/kin-openapi
Pull request #84 (2019-04-02 13:50:32)

Decoding of request body with content types "application/x-www-form-urlencoded" and "multipart/form-data"
Hello. 

The PR adds support for decoding of request body with content types `"application/x-www-form-urlencoded"` and `"multipart/form-data"`. Also I add a decoder's implementation for files (see `openapi3filter.FileBodyDecoder`). 

The PR extends functional that appeared in [PR-78](getkin#78).


Patch:
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index 8ac3ca3..6959cb5 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -2,6 +2,7 @@ package openapi3
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/getkin/kin-openapi/jsoninfo"
 )
@@ -13,7 +14,7 @@ type Encoding struct {
 	ContentType   string                `json:"contentType,omitempty"`
 	Headers       map[string]*HeaderRef `json:"headers,omitempty"`
 	Style         string                `json:"style,omitempty"`
-	Explode       bool                  `json:"explode,omitempty"`
+	Explode       *bool                 `json:"explode,omitempty"`
 	AllowReserved bool                  `json:"allowReserved,omitempty"`
 }
 
@@ -45,6 +46,21 @@ func (encoding *Encoding) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, encoding)
 }
 
+// SerializationMethod returns a serialization method of request body.
+// When serialization method is not defined the method returns the default serialization method.
+func (encoding *Encoding) SerializationMethod() *SerializationMethod {
+	sm := &SerializationMethod{Style: SerializationForm, Explode: true}
+	if encoding != nil {
+		if encoding.Style != "" {
+			sm.Style = encoding.Style
+		}
+		if encoding.Explode != nil {
+			sm.Explode = *encoding.Explode
+		}
+	}
+	return sm
+}
+
 func (encoding *Encoding) Validate(c context.Context) error {
 	if encoding == nil {
 		return nil
@@ -57,5 +73,21 @@ func (encoding *Encoding) Validate(c context.Context) error {
 			return nil
 		}
 	}
+
+	// Validate a media types's serialization method.
+	sm := encoding.SerializationMethod()
+	switch {
+	case sm.Style == SerializationForm && sm.Explode,
+		sm.Style == SerializationForm && !sm.Explode,
+		sm.Style == SerializationSpaceDelimited && sm.Explode,
+		sm.Style == SerializationSpaceDelimited && !sm.Explode,
+		sm.Style == SerializationPipeDelimited && sm.Explode,
+		sm.Style == SerializationPipeDelimited && !sm.Explode,
+		sm.Style == SerializationDeepObject && sm.Explode:
+		// it is a valid
+	default:
+		return fmt.Errorf("Serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
+	}
+
 	return nil
 }
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 8a469d0..32d4bbb 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -73,16 +73,6 @@ 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,
@@ -141,11 +131,6 @@ 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.
diff --git a/openapi3/serialization_method.go b/openapi3/serialization_method.go
new file mode 100644
index 0000000..2ec8bd2
--- /dev/null
+++ b/openapi3/serialization_method.go
@@ -0,0 +1,17 @@
+package openapi3
+
+const (
+	SerializationSimple         = "simple"
+	SerializationLabel          = "label"
+	SerializationMatrix         = "matrix"
+	SerializationForm           = "form"
+	SerializationSpaceDelimited = "spaceDelimited"
+	SerializationPipeDelimited  = "pipeDelimited"
+	SerializationDeepObject     = "deepObject"
+)
+
+// SerializationMethod describes a serialization method of HTTP request's parameters and body.
+type SerializationMethod struct {
+	Style   string
+	Explode bool
+}
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index f62b6eb..dee4f23 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -3,7 +3,12 @@ package openapi3filter
 import (
 	"encoding/json"
 	"fmt"
+	"io"
+	"io/ioutil"
+	"mime"
+	"mime/multipart"
 	"net/http"
+	"net/url"
 	"regexp"
 	"strconv"
 	"strings"
@@ -11,8 +16,6 @@ import (
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
-const errMsgInvalidSerializationF = "%s parameter %q has an invalid serialization method: style=%q, explode=%v"
-
 // ParseErrorKind describes a kind of ParseError.
 // The type simplifies comparison of errors.
 type ParseErrorKind int
@@ -25,28 +28,33 @@ const (
 	// KindInvalidFormat describes an error that happens when a value does not conform a format
 	// that is required by a serialization method.
 	KindInvalidFormat
-	// KindInvalidInt describes an error that happens when a value is an invalid integer.
-	KindInvalidInt
-	// KindInvalidNumber describes an error that happens when a value is an invalid number.
-	KindInvalidNumber
-	// KindInvalidBool describes an error that happens when a value is an invalid boolean.
-	KindInvalidBool
 )
 
 // ParseError describes errors which happens while parse operation's parameters, requestBody, or response.
 type ParseError struct {
 	Kind   ParseErrorKind
-	Path   []interface{}
 	Value  interface{}
 	Reason string
 	Cause  error
+
+	path []interface{}
 }
 
 func (e *ParseError) Error() string {
 	var msg []string
-	if e.Path != nil {
-		msg = append(msg, fmt.Sprintf("path %v", e.Path))
+	if p := e.Path(); len(p) > 0 {
+		var arr []string
+		for _, v := range p {
+			arr = append(arr, fmt.Sprintf("%v", v))
+		}
+		msg = append(msg, fmt.Sprintf("path %v", strings.Join(arr, ".")))
 	}
+	msg = append(msg, e.innerError())
+	return strings.Join(msg, ": ")
+}
+
+func (e *ParseError) innerError() string {
+	var msg []string
 	if e.Value != nil {
 		msg = append(msg, fmt.Sprintf("value %v", e.Value))
 	}
@@ -54,53 +62,97 @@ func (e *ParseError) Error() string {
 		msg = append(msg, e.Reason)
 	}
 	if e.Cause != nil {
-		msg = append(msg, e.Cause.Error())
+		if v, ok := e.Cause.(*ParseError); ok {
+			msg = append(msg, v.innerError())
+		} else {
+			msg = append(msg, e.Cause.Error())
+		}
 	}
 	return strings.Join(msg, ": ")
 }
 
+// RootCause returns a root cause of ParseError.
+func (e *ParseError) RootCause() error {
+	if v, ok := e.Cause.(*ParseError); ok {
+		return v.RootCause()
+	}
+	return e.Cause
+}
+
+// Path returns a path to the root cause.
+func (e *ParseError) Path() []interface{} {
+	var path []interface{}
+	if v, ok := e.Cause.(*ParseError); ok {
+		p := v.Path()
+		if len(p) > 0 {
+			path = append(path, p...)
+		}
+	}
+	if len(e.path) > 0 {
+		path = append(path, e.path...)
+	}
+	return path
+}
+
+func invalidSerializationMethodErr(sm *openapi3.SerializationMethod) error {
+	return fmt.Errorf("invalid serialization method: style=%q, explode=%v", sm.Style, sm.Explode)
+}
+
+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)
+}
+
 // decodeParameter returns a value of an operation's parameter from HTTP request.
 // The function returns ParseError when HTTP request contains an invalid value of a parameter.
 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)
+	sm, err := param.SerializationMethod()
+	if err != nil {
+		return nil, err
 	}
 
+	var dec valueDecoder
 	switch param.In {
 	case openapi3.ParameterInPath:
-		decoder = &pathParamDecoder{input: input}
+		dec = &pathParamDecoder{pathParams: input.PathParams}
 	case openapi3.ParameterInQuery:
-		decoder = &queryParamDecoder{input: input}
+		dec = &urlValuesDecoder{values: input.GetQueryParams()}
 	case openapi3.ParameterInHeader:
-		decoder = &headerParamDecoder{input: input}
+		dec = &headerParamDecoder{header: input.Request.Header}
 	case openapi3.ParameterInCookie:
-		decoder = &cookieParamDecoder{input: input}
+		dec = &cookieParamDecoder{req: input.Request}
 	default:
 		return nil, fmt.Errorf("unsupported parameter's 'in': %s", param.In)
 	}
 
-	switch param.Schema.Value.Type {
+	return decodeValue(dec, param.Name, sm, param.Schema)
+}
+
+func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
+	var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error)
+	switch schema.Value.Type {
 	case "array":
-		return decoder.DecodeArray(param)
+		decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
+			return dec.DecodeArray(param, sm, schema)
+		}
 	case "object":
-		return decoder.DecodeObject(param)
+		decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
+			return dec.DecodeObject(param, sm, schema)
+		}
 	default:
-		return decoder.DecodePrimitive(param)
+		decodeFn = dec.DecodePrimitive
 	}
+
+	return decodeFn(param, sm, schema)
 }
 
 // pathParamDecoder decodes values of path parameters.
 type pathParamDecoder struct {
-	input *RequestValidationInput
+	pathParams map[string]string
 }
 
-func (d *pathParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
 	var prefix string
 	switch sm.Style {
 	case "simple":
@@ -108,16 +160,16 @@ func (d *pathParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface
 	case "label":
 		prefix = "."
 	case "matrix":
-		prefix = ";" + param.Name + "="
+		prefix = ";" + param + "="
 	default:
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	if d.input.PathParams == nil {
+	if d.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)]
+	raw, ok := d.pathParams[paramKey(param, sm)]
 	if !ok || raw == "" {
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
@@ -126,14 +178,10 @@ func (d *pathParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface
 	if err != nil {
 		return nil, err
 	}
-	return parsePrimitive(src, param.Schema)
+	return parsePrimitive(src, schema)
 }
 
-func (d *pathParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) {
 	var prefix, delim string
 	switch {
 	case sm.Style == "simple":
@@ -145,20 +193,20 @@ func (d *pathParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}
 		prefix = "."
 		delim = "."
 	case sm.Style == "matrix" && sm.Explode == false:
-		prefix = ";" + param.Name + "="
+		prefix = ";" + param + "="
 		delim = ","
 	case sm.Style == "matrix" && sm.Explode == true:
-		prefix = ";" + param.Name + "="
-		delim = ";" + param.Name + "="
+		prefix = ";" + param + "="
+		delim = ";" + param + "="
 	default:
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	if d.input.PathParams == nil {
+	if d.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)]
+	raw, ok := d.pathParams[paramKey(param, sm)]
 	if !ok || raw == "" {
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
@@ -167,14 +215,10 @@ func (d *pathParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}
 	if err != nil {
 		return nil, err
 	}
-	return parseArray(strings.Split(src, delim), param.Schema)
+	return parseArray(strings.Split(src, delim), schema)
 }
 
-func (d *pathParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
 	var prefix, propsDelim, valueDelim string
 	switch {
 	case sm.Style == "simple" && sm.Explode == false:
@@ -192,7 +236,7 @@ func (d *pathParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]i
 		propsDelim = "."
 		valueDelim = "="
 	case sm.Style == "matrix" && sm.Explode == false:
-		prefix = ";" + param.Name + "="
+		prefix = ";" + param + "="
 		propsDelim = ","
 		valueDelim = ","
 	case sm.Style == "matrix" && sm.Explode == true:
@@ -200,14 +244,14 @@ func (d *pathParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]i
 		propsDelim = ";"
 		valueDelim = "="
 	default:
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	if d.input.PathParams == nil {
+	if d.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)]
+	raw, ok := d.pathParams[paramKey(param, sm)]
 	if !ok || raw == "" {
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
@@ -220,18 +264,18 @@ func (d *pathParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]i
 	if err != nil {
 		return nil, err
 	}
-	return makeObject(props, param.Schema)
+	return makeObject(props, schema)
 }
 
 // paramKey returns a key to get a raw value of a path parameter.
-func (d *pathParamDecoder) paramKey(param *openapi3.Parameter, sm *openapi3.SerializationMethod) string {
+func paramKey(param string, sm *openapi3.SerializationMethod) string {
 	switch sm.Style {
 	case "label":
-		return "." + param.Name
+		return "." + param
 	case "matrix":
-		return ";" + param.Name
+		return ";" + param
 	default:
-		return param.Name
+		return param
 	}
 }
 
@@ -251,38 +295,30 @@ func cutPrefix(raw, prefix string) (string, error) {
 	return raw[len(prefix):], nil
 }
 
-// queryParamDecoder decodes values of query parameters.
-type queryParamDecoder struct {
-	input *RequestValidationInput
+// urlValuesDecoder decodes values of query parameters.
+type urlValuesDecoder struct {
+	values url.Values
 }
 
-func (d *queryParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
 	if sm.Style != "form" {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	values := d.input.GetQueryParams()[param.Name]
+	values := d.values[param]
 	if len(values) == 0 {
 		// HTTP request does not contain a value of the target query parameter.
 		return nil, nil
 	}
-	return parsePrimitive(values[0], param.Schema)
+	return parsePrimitive(values[0], schema)
 }
 
-func (d *queryParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) {
 	if sm.Style == "deepObject" {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	values := d.input.GetQueryParams()[param.Name]
+	values := d.values[param]
 	if len(values) == 0 {
 		// HTTP request does not contain a value of the target query parameter.
 		return nil, nil
@@ -299,18 +335,14 @@ func (d *queryParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{
 		}
 		values = strings.Split(values[0], delim)
 	}
-	return parseArray(values, param.Schema)
+	return parseArray(values, schema)
 }
 
-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
-	}
+func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
+	var propsFn func(url.Values) (map[string]string, error)
 	switch sm.Style {
 	case "form":
-		propsFn = func(params map[string][]string) (map[string]string, error) {
+		propsFn = func(params url.Values) (map[string]string, error) {
 			if len(params) == 0 {
 				// HTTP request does not contain query parameters.
 				return nil, nil
@@ -322,7 +354,7 @@ func (d *queryParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]
 				}
 				return props, nil
 			}
-			values := params[param.Name]
+			values := params[param]
 			if len(values) == 0 {
 				// HTTP request does not contain a value of the target query parameter.
 				return nil, nil
@@ -330,10 +362,10 @@ func (d *queryParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]
 			return propsFromString(values[0], ",", ",")
 		}
 	case "deepObject":
-		propsFn = func(params map[string][]string) (map[string]string, error) {
+		propsFn = func(params url.Values) (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)
+				groups := regexp.MustCompile(fmt.Sprintf("%s\\[(.+?)\\]", param)).FindAllStringSubmatch(key, -1)
 				if len(groups) == 0 {
 					// A query parameter's name does not match the required format, so skip it.
 					continue
@@ -347,68 +379,56 @@ func (d *queryParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]
 			return props, nil
 		}
 	default:
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	props, err := propsFn(d.input.GetQueryParams())
+	props, err := propsFn(d.values)
 	if err != nil {
 		return nil, err
 	}
 	if props == nil {
 		return nil, nil
 	}
-	return makeObject(props, param.Schema)
+	return makeObject(props, schema)
 }
 
 // headerParamDecoder decodes values of header parameters.
 type headerParamDecoder struct {
-	input *RequestValidationInput
+	header http.Header
 }
 
-func (d *headerParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
 	if sm.Style != "simple" {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	raw := d.input.Request.Header.Get(http.CanonicalHeaderKey(param.Name))
-	return parsePrimitive(raw, param.Schema)
+	raw := d.header.Get(http.CanonicalHeaderKey(param))
+	return parsePrimitive(raw, schema)
 }
 
-func (d *headerParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) {
 	if sm.Style != "simple" {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	raw := d.input.Request.Header.Get(http.CanonicalHeaderKey(param.Name))
+	raw := d.header.Get(http.CanonicalHeaderKey(param))
 	if raw == "" {
 		// HTTP request does not contains a corresponding header
 		return nil, nil
 	}
-	return parseArray(strings.Split(raw, ","), param.Schema)
+	return parseArray(strings.Split(raw, ","), schema)
 }
 
-func (d *headerParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *headerParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
 	if sm.Style != "simple" {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 	valueDelim := ","
 	if sm.Explode {
 		valueDelim = "="
 	}
 
-	raw := d.input.Request.Header.Get(http.CanonicalHeaderKey(param.Name))
+	raw := d.header.Get(http.CanonicalHeaderKey(param))
 	if raw == "" {
 		// HTTP request does not contain a corresponding header.
 		return nil, nil
@@ -417,76 +437,64 @@ func (d *headerParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string
 	if err != nil {
 		return nil, err
 	}
-	return makeObject(props, param.Schema)
+	return makeObject(props, schema)
 }
 
 // cookieParamDecoder decodes values of cookie parameters.
 type cookieParamDecoder struct {
-	input *RequestValidationInput
+	req *http.Request
 }
 
-func (d *cookieParamDecoder) DecodePrimitive(param *openapi3.Parameter) (interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, error) {
 	if sm.Style != "form" {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	cookie, err := d.input.Request.Cookie(param.Name)
+	cookie, err := d.req.Cookie(param)
 	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)
+		return nil, fmt.Errorf("decode param %q: %s", param, err)
 	}
-	return parsePrimitive(cookie.Value, param.Schema)
+	return parsePrimitive(cookie.Value, schema)
 }
 
-func (d *cookieParamDecoder) DecodeArray(param *openapi3.Parameter) ([]interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, error) {
 	if sm.Style != "form" || sm.Explode {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	cookie, err := d.input.Request.Cookie(param.Name)
+	cookie, err := d.req.Cookie(param)
 	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)
+		return nil, fmt.Errorf("decode param %q: %s", param, err)
 	}
-	return parseArray(strings.Split(cookie.Value, ","), param.Schema)
+	return parseArray(strings.Split(cookie.Value, ","), schema)
 }
 
-func (d *cookieParamDecoder) DecodeObject(param *openapi3.Parameter) (map[string]interface{}, error) {
-	sm, err := param.SerializationMethod()
-	if err != nil {
-		return nil, err
-	}
+func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
 	if sm.Style != "form" || sm.Explode {
-		return nil, fmt.Errorf(errMsgInvalidSerializationF, param.In, param.Name, sm.Style, sm.Explode)
+		return nil, invalidSerializationMethodErr(sm)
 	}
 
-	cookie, err := d.input.Request.Cookie(param.Name)
+	cookie, err := d.req.Cookie(param)
 	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)
+		return nil, fmt.Errorf("decode param %q: %s", param, err)
 	}
 	props, err := propsFromString(cookie.Value, ",", ",")
 	if err != nil {
 		return nil, err
 	}
-	return makeObject(props, param.Schema)
+	return makeObject(props, schema)
 }
 
 // propsFromString returns a properties map that is created by splitting a source string by propDelim and valueDelim.
@@ -539,9 +547,9 @@ func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string
 		value, err := parsePrimitive(props[propName], propSchema)
 		if err != nil {
 			if v, ok := err.(*ParseError); ok {
-				return nil, &ParseError{Path: []interface{}{propName}, Cause: v}
+				return nil, &ParseError{path: []interface{}{propName}, Cause: v}
 			}
-			return nil, err
+			return nil, fmt.Errorf("property %q: %s", propName, err)
 		}
 		obj[propName] = value
 	}
@@ -557,9 +565,9 @@ func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, err
 		item, err := parsePrimitive(v, schemaRef.Value.Items)
 		if err != nil {
 			if v, ok := err.(*ParseError); ok {
-				return nil, &ParseError{Path: []interface{}{i}, Cause: v}
+				return nil, &ParseError{path: []interface{}{i}, Cause: v}
 			}
-			return nil, err
+			return nil, fmt.Errorf("item %d: %s", i, err)
 		}
 		value = append(value, item)
 	}
@@ -577,19 +585,19 @@ func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error)
 	case "integer":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidInt, Value: raw, Reason: "an invalid interger", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid interger", Cause: err}
 		}
 		return v, nil
 	case "number":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidNumber, Value: raw, Reason: "an invalid number", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err}
 		}
 		return v, nil
 	case "boolean":
 		v, err := strconv.ParseBool(raw)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidBool, Value: raw, Reason: "an invalid number", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err}
 		}
 		return v, nil
 	case "string":
@@ -599,24 +607,16 @@ func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error)
 	}
 }
 
+// EncodingFn is a function that returns an encoding of a request body's part.
+type EncodingFn func(partName string) *openapi3.Encoding
+
 // BodyDecoder is an interface to decode a body of a request or response.
 // An implementation must return a value that is a primitive, []interface{}, or map[string]interface{}.
-type BodyDecoder func(data []byte) (interface{}, error)
+type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) (interface{}, error)
 
 // bodyDecoders contains decoders for supported content types of a body.
 // By default, there is content type "application/json" is supported only.
-var bodyDecoders = map[string]BodyDecoder{
-	"plain/text": func(body []byte) (interface{}, error) {
-		return string(body), nil
-	},
-	"application/json": func(body []byte) (interface{}, error) {
-		var value interface{}
-		if err := json.Unmarshal(body, &value); err != nil {
-			return nil, err
-		}
-		return value, nil
-	},
-}
+var bodyDecoders = make(map[string]BodyDecoder)
 
 // RegisterBodyDecoder registers a request body's decoder for a content type.
 //
@@ -644,17 +644,167 @@ func UnregisterBodyDecoder(contentType string) {
 
 // decodeBody returns a decoded body.
 // The function returns ParseError when a body is invalid.
-func decodeBody(body []byte, contentType string) (interface{}, error) {
-	decoder, ok := bodyDecoders[contentType]
+func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+	contentType := header.Get(http.CanonicalHeaderKey("Content-Type"))
+	mediaType := parseMediaType(contentType)
+	decoder, ok := bodyDecoders[mediaType]
 	if !ok {
 		return nil, &ParseError{
 			Kind:   KindUnsupportedFormat,
-			Reason: fmt.Sprintf("an unsupported content type %q", contentType),
+			Reason: fmt.Sprintf("unsupported content type %q", mediaType),
 		}
 	}
-	value, err := decoder(body)
+	value, err := decoder(body, header, schema, encFn)
 	if err != nil {
+		return nil, err
+	}
+	return value, nil
+}
+
+func init() {
+	RegisterBodyDecoder("plain/text", plainBodyDecoder)
+	RegisterBodyDecoder("application/json", jsonBodyDecoder)
+	RegisterBodyDecoder("application/x-www-form-urlencoded", urlencodedBodyDecoder)
+	RegisterBodyDecoder("multipart/form-data", multipartBodyDecoder)
+	RegisterBodyDecoder("application/octet-stream", FileBodyDecoder)
+}
+
+func plainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+	data, err := ioutil.ReadAll(body)
+	if err != nil {
+		return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
+	}
+	return string(data), nil
+}
+
+func jsonBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+	var value interface{}
+	if err := json.NewDecoder(body).Decode(&value); err != nil {
 		return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
 	}
 	return value, nil
 }
+
+func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+	// Validate JSON schema of request body.
+	// By the OpenAPI 3 specification request body's schema must have type "object".
+	// Properties of the schema describes individual parts of request body.
+	if schema.Value.Type != "object" {
+		return nil, fmt.Errorf("unsupported JSON schema of request body")
+	}
+	for propName, propSchema := range schema.Value.Properties {
+		switch propSchema.Value.Type {
+		case "object":
+			return nil, fmt.Errorf("unsupported JSON schema of request body's property %q", propName)
+		case "array":
+			items := propSchema.Value.Items.Value
+			if items.Type != "string" && items.Type != "integer" && items.Type != "number" && items.Type != "boolean" {
+				return nil, fmt.Errorf("unsupported JSON schema of request body's property %q", propName)
+			}
+		}
+	}
+
+	// Parse form.
+	b, err := ioutil.ReadAll(body)
+	if err != nil {
+		return nil, err
+	}
+	values, err := url.ParseQuery(string(b))
+	if err != nil {
+		return nil, err
+	}
+
+	// Make an object value from form values.
+	obj := make(map[string]interface{})
+	dec := &urlValuesDecoder{values: values}
+	for name, prop := range schema.Value.Properties {
+		var (
+			value interface{}
+			enc   *openapi3.Encoding
+		)
+		if encFn != nil {
+			enc = encFn(name)
+		}
+		sm := enc.SerializationMethod()
+
+		if value, err = decodeValue(dec, name, sm, prop); err != nil {
+			return nil, err
+		}
+		obj[name] = value
+	}
+
+	return obj, nil
+}
+
+func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+	if schema.Value.Type != "object" {
+		return nil, fmt.Errorf("unsupported JSON schema of request body")
+	}
+
+	// Parse form.
+	values := make(map[string][]interface{})
+	contentType := header.Get(http.CanonicalHeaderKey("Content-Type"))
+	_, params, err := mime.ParseMediaType(contentType)
+	if err != nil {
+		return nil, err
+	}
+	mr := multipart.NewReader(body, params["boundary"])
+	for {
+		var part *multipart.Part
+		if part, err = mr.NextPart(); err == io.EOF {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+
+		var (
+			name = part.FormName()
+			enc  *openapi3.Encoding
+		)
+		if encFn != nil {
+			enc = encFn(name)
+		}
+		subEncFn := func(string) *openapi3.Encoding { return enc }
+		// If the property's schema has type "array" it is means that the form contains a few parts with the same name.
+		// Every such part has a type that is defined by an items schema in the property's schema.
+		valueSchema := schema.Value.Properties[name]
+		if valueSchema.Value.Type == "array" {
+			valueSchema = valueSchema.Value.Items
+		}
+
+		var value interface{}
+		if value, err = decodeBody(part, http.Header(part.Header), valueSchema, subEncFn); err != nil {
+			if v, ok := err.(*ParseError); ok {
+				return nil, &ParseError{path: []interface{}{name}, Cause: v}
+			}
+			return nil, fmt.Errorf("part %s: %s", name, err)
+		}
+		values[name] = append(values[name], value)
+	}
+
+	// Make an object value from form values.
+	obj := make(map[string]interface{})
+	for name, prop := range schema.Value.Properties {
+		vv := values[name]
+		if len(vv) == 0 {
+			continue
+		}
+		if prop.Value.Type == "array" {
+			obj[name] = vv
+		} else {
+			obj[name] = vv[0]
+		}
+	}
+
+	return obj, nil
+}
+
+// FileBodyDecoder is a body decoder that decodes a file body to a string.
+func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+	data, err := ioutil.ReadAll(body)
+	if err != nil {
+		return nil, err
+	}
+	return string(data), nil
+}
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 23c7273..8e99c85 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -141,7 +141,7 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque
 		return &RequestError{
 			Input:       input,
 			RequestBody: requestBody,
-			Reason:      "content type of request body is missed",
+			Reason:      "content type is missed",
 		}
 	}
 
@@ -154,13 +154,13 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque
 		}
 	}
 
-	schemaRef := contentType.Schema
-	if schemaRef == nil {
+	if contentType.Schema == nil {
 		// A JSON schema that describes the received data is not declared, so skip validation.
 		return nil
 	}
 
-	value, err := decodeBody(data, mediaType)
+	encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
+	value, err := decodeBody(bytes.NewReader(data), req.Header, contentType.Schema, encFn)
 	if err != nil {
 		return &RequestError{
 			Input:       input,
@@ -171,7 +171,7 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque
 	}
 
 	// Validate JSON with the schema
-	if err := schemaRef.Value.VisitJSON(value); err != nil {
+	if err := contentType.Schema.Value.VisitJSON(value); err != nil {
 		return &RequestError{
 			Input:       input,
 			RequestBody: requestBody,
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index 8dd078b..33bef79 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -2,10 +2,13 @@
 package openapi3filter
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"io/ioutil"
 	"net/http"
+
+	"github.com/getkin/kin-openapi/openapi3"
 )
 
 func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
@@ -88,8 +91,7 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
 		}
 	}
 
-	schema := contentType.Schema
-	if schema == nil {
+	if contentType.Schema == nil {
 		// An operation does not contains a validation schema for responses with this status code.
 		return nil
 	}
@@ -118,7 +120,8 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
 	// Put the data back into the response.
 	input.SetBodyBytes(data)
 
-	value, err := decodeBody(data, mediaType)
+	encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
+	value, err := decodeBody(bytes.NewBuffer(data), input.Header, contentType.Schema, encFn)
 	if err != nil {
 		return &ResponseError{
 			Input:  input,
@@ -128,7 +131,7 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
 	}
 
 	// Validate data with the schema.
-	if err := schema.Value.VisitJSON(value); err != nil {
+	if err := contentType.Schema.Value.VisitJSON(value); err != nil {
 		return &ResponseError{
 			Input:  input,
 			Reason: "response body doesn't match the schema",


Test patch:
diff --git a/openapi3/encoding_test.go b/openapi3/encoding_test.go
index d287ced..67e7b6b 100644
--- a/openapi3/encoding_test.go
+++ b/openapi3/encoding_test.go
@@ -3,6 +3,7 @@ package openapi3_test
 import (
 	"context"
 	"encoding/json"
+	"reflect"
 	"testing"
 
 	"github.com/getkin/kin-openapi/openapi3"
@@ -38,13 +39,14 @@ var encodingJSON = []byte(`
   "headers": {
     "someHeader": {}
   },
-  "style": "simple",
+  "style": "form",
   "explode": true,
   "allowReserved": true
 }
 `)
 
 func encoding() *openapi3.Encoding {
+	explode := true
 	return &openapi3.Encoding{
 		ContentType: "application/json",
 		Headers: map[string]*openapi3.HeaderRef{
@@ -52,8 +54,48 @@ func encoding() *openapi3.Encoding {
 				Value: &openapi3.Header{},
 			},
 		},
-		Style:         "simple",
-		Explode:       true,
+		Style:         "form",
+		Explode:       &explode,
 		AllowReserved: true,
 	}
 }
+
+func TestEncodingSerializationMethod(t *testing.T) {
+	boolPtr := func(b bool) *bool { return &b }
+	testCases := []struct {
+		name string
+		enc  *openapi3.Encoding
+		want *openapi3.SerializationMethod
+	}{
+		{
+			name: "default",
+			want: &openapi3.SerializationMethod{Style: openapi3.SerializationForm, Explode: true},
+		},
+		{
+			name: "encoding with style",
+			enc:  &openapi3.Encoding{Style: openapi3.SerializationSpaceDelimited},
+			want: &openapi3.SerializationMethod{Style: openapi3.SerializationSpaceDelimited, Explode: true},
+		},
+		{
+			name: "encoding with explode",
+			enc:  &openapi3.Encoding{Explode: boolPtr(true)},
+			want: &openapi3.SerializationMethod{Style: openapi3.SerializationForm, Explode: true},
+		},
+		{
+			name: "encoding with no explode",
+			enc:  &openapi3.Encoding{Explode: boolPtr(false)},
+			want: &openapi3.SerializationMethod{Style: openapi3.SerializationForm, Explode: false},
+		},
+		{
+			name: "encoding with style and explode ",
+			enc:  &openapi3.Encoding{Style: openapi3.SerializationSpaceDelimited, Explode: boolPtr(false)},
+			want: &openapi3.SerializationMethod{Style: openapi3.SerializationSpaceDelimited, Explode: false},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := tc.enc.SerializationMethod()
+			require.True(t, reflect.DeepEqual(got, tc.want), "got %#v, want %#v", got, tc.want)
+		})
+	}
+}
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 1ac280f..86fefe0 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -1,7 +1,15 @@
 package openapi3filter
 
 import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"mime/multipart"
 	"net/http"
+	"net/textproto"
+	"net/url"
 	"reflect"
 	"strings"
 	"testing"
@@ -139,7 +147,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "integer invalid",
 					param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema},
 					path:  "/foo",
-					err:   &ParseError{Kind: KindInvalidInt, Value: "foo"},
+					err:   &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:  "number",
@@ -151,7 +159,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "number invalid",
 					param: &openapi3.Parameter{Name: "param", In: "path", Schema: numberSchema},
 					path:  "/foo",
-					err:   &ParseError{Kind: KindInvalidNumber, Value: "foo"},
+					err:   &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:  "boolean",
@@ -163,7 +171,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "boolean invalid",
 					param: &openapi3.Parameter{Name: "param", In: "path", Schema: booleanSchema},
 					path:  "/foo",
-					err:   &ParseError{Kind: KindInvalidBool, Value: "foo"},
+					err:   &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 			},
 		},
@@ -240,19 +248,19 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "invalid integer items",
 					param: &openapi3.Parameter{Name: "param", In: "path", Schema: arrayOf(integerSchema)},
 					path:  "/1,foo",
-					err:   &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidInt, Value: "foo"}},
+					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",
-					err:   &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "foo"}},
+					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",
-					err:   &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidBool, Value: "foo"}},
+					err:   &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
 				},
 			},
 		},
@@ -329,19 +337,19 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "invalid integer prop",
 					param: &openapi3.Parameter{Name: "param", In: "path", Schema: objectOf("foo", integerSchema)},
 					path:  "/foo,bar",
-					err:   &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidInt, Value: "bar"}},
+					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",
-					err:   &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "bar"}},
+					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",
-					err:   &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidBool, Value: "bar"}},
+					err:   &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
 				},
 			},
 		},
@@ -382,7 +390,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "integer invalid",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema},
 					query: "param=foo",
-					err:   &ParseError{Kind: KindInvalidInt, Value: "foo"},
+					err:   &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:  "number",
@@ -394,7 +402,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "number invalid",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: numberSchema},
 					query: "param=foo",
-					err:   &ParseError{Kind: KindInvalidNumber, Value: "foo"},
+					err:   &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:  "boolean",
@@ -406,7 +414,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "boolean invalid",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: booleanSchema},
 					query: "param=foo",
-					err:   &ParseError{Kind: KindInvalidBool, Value: "foo"},
+					err:   &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 			},
 		},
@@ -459,19 +467,19 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "invalid integer items",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: arrayOf(integerSchema)},
 					query: "param=1&param=foo",
-					err:   &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidInt, Value: "foo"}},
+					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&param=foo",
-					err:   &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "foo"}},
+					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&param=foo",
-					err:   &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidBool, Value: "foo"}},
+					err:   &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
 				},
 			},
 		},
@@ -506,19 +514,19 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "invalid integer prop",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: objectOf("foo", integerSchema)},
 					query: "foo=bar",
-					err:   &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidInt, Value: "bar"}},
+					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",
-					err:   &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "bar"}},
+					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",
-					err:   &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidBool, Value: "bar"}},
+					err:   &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
 				},
 			},
 		},
@@ -559,7 +567,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "integer invalid",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
 					header: "X-Param:foo",
-					err:    &ParseError{Kind: KindInvalidInt, Value: "foo"},
+					err:    &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:   "number",
@@ -571,7 +579,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "number invalid",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: numberSchema},
 					header: "X-Param:foo",
-					err:    &ParseError{Kind: KindInvalidNumber, Value: "foo"},
+					err:    &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:   "boolean",
@@ -583,7 +591,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "boolean invalid",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: booleanSchema},
 					header: "X-Param:foo",
-					err:    &ParseError{Kind: KindInvalidBool, Value: "foo"},
+					err:    &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 			},
 		},
@@ -612,19 +620,19 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "invalid integer items",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: arrayOf(integerSchema)},
 					header: "X-Param:1,foo",
-					err:    &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidInt, Value: "foo"}},
+					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",
-					err:    &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "foo"}},
+					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",
-					err:    &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidBool, Value: "foo"}},
+					err:    &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
 				},
 			},
 		},
@@ -653,19 +661,19 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "invalid integer prop",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", integerSchema)},
 					header: "X-Param:foo,bar",
-					err:    &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidInt, Value: "bar"}},
+					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",
-					err:    &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "bar"}},
+					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",
-					err:    &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidBool, Value: "bar"}},
+					err:    &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
 				},
 			},
 		},
@@ -706,7 +714,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "integer invalid",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema},
 					cookie: "X-Param:foo",
-					err:    &ParseError{Kind: KindInvalidInt, Value: "foo"},
+					err:    &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:   "number",
@@ -718,7 +726,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "number invalid",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: numberSchema},
 					cookie: "X-Param:foo",
-					err:    &ParseError{Kind: KindInvalidNumber, Value: "foo"},
+					err:    &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 				{
 					name:   "boolean",
@@ -730,7 +738,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "boolean invalid",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: booleanSchema},
 					cookie: "X-Param:foo",
-					err:    &ParseError{Kind: KindInvalidBool, Value: "foo"},
+					err:    &ParseError{Kind: KindInvalidFormat, Value: "foo"},
 				},
 			},
 		},
@@ -747,19 +755,19 @@ func TestDecodeParameter(t *testing.T) {
 					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:    &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidInt, Value: "foo"}},
+					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",
-					err:    &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "foo"}},
+					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",
-					err:    &ParseError{Path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidBool, Value: "foo"}},
+					err:    &ParseError{path: []interface{}{1}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "foo"}},
 				},
 			},
 		},
@@ -776,19 +784,19 @@ func TestDecodeParameter(t *testing.T) {
 					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:    &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidInt, Value: "bar"}},
+					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",
-					err:    &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidNumber, Value: "bar"}},
+					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",
-					err:    &ParseError{Path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidBool, Value: "bar"}},
+					err:    &ParseError{path: []interface{}{"foo"}, Cause: &ParseError{Kind: KindInvalidFormat, Value: "bar"}},
 				},
 			},
 		},
@@ -863,12 +871,44 @@ func TestDecodeParameter(t *testing.T) {
 }
 
 func TestDecodeBody(t *testing.T) {
+	boolPtr := func(b bool) *bool { return &b }
+
+	urlencodedForm := make(url.Values)
+	urlencodedForm.Set("a", "a1")
+	urlencodedForm.Set("b", "10")
+	urlencodedForm.Add("c", "c1")
+	urlencodedForm.Add("c", "c2")
+
+	urlencodedSpaceDelim := make(url.Values)
+	urlencodedSpaceDelim.Set("a", "a1")
+	urlencodedSpaceDelim.Set("b", "10")
+	urlencodedSpaceDelim.Add("c", "c1 c2")
+
+	urlencodedPipeDelim := make(url.Values)
+	urlencodedPipeDelim.Set("a", "a1")
+	urlencodedPipeDelim.Set("b", "10")
+	urlencodedPipeDelim.Add("c", "c1|c2")
+
+	d, err := json.Marshal(map[string]interface{}{"d1": "d1"})
+	require.NoError(t, err)
+	multipartForm, multipartFormMime, err := newTestMultipartForm([]*testFormPart{
+		{name: "a", contentType: "plain/text", data: strings.NewReader("a1")},
+		{name: "b", contentType: "application/json", data: strings.NewReader("10")},
+		{name: "c", contentType: "plain/text", data: strings.NewReader("c1")},
+		{name: "c", contentType: "plain/text", data: strings.NewReader("c2")},
+		{name: "d", contentType: "application/json", data: bytes.NewReader(d)},
+		{name: "f", contentType: "application/octet-stream", data: strings.NewReader("foo"), filename: "f1"},
+	})
+	require.NoError(t, err)
+
 	testCases := []struct {
-		name    string
-		mime    string
-		body    []byte
-		want    interface{}
-		wantErr error
+		name     string
+		mime     string
+		body     io.Reader
+		schema   *openapi3.Schema
+		encoding map[string]*openapi3.Encoding
+		want     interface{}
+		wantErr  error
 	}{
 		{
 			name:    "unsupported content type",
@@ -878,19 +918,91 @@ func TestDecodeBody(t *testing.T) {
 		{
 			name:    "invalid body data",
 			mime:    "application/json",
-			body:    []byte("invalid"),
+			body:    strings.NewReader("invalid"),
 			wantErr: &ParseError{Kind: KindInvalidFormat},
 		},
 		{
-			name: "valid body data",
+			name: "plain text",
+			mime: "plain/text",
+			body: strings.NewReader("text"),
+			want: "text",
+		},
+		{
+			name: "json",
 			mime: "application/json",
-			body: []byte("\"foo\""),
+			body: strings.NewReader("\"foo\""),
+			want: "foo",
+		},
+		{
+			name: "urlencoded form",
+			mime: "application/x-www-form-urlencoded",
+			body: strings.NewReader(urlencodedForm.Encode()),
+			schema: openapi3.NewObjectSchema().
+				WithProperty("a", openapi3.NewStringSchema()).
+				WithProperty("b", openapi3.NewIntegerSchema()).
+				WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())),
+			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}},
+		},
+		{
+			name: "urlencoded space delimited",
+			mime: "application/x-www-form-urlencoded",
+			body: strings.NewReader(urlencodedSpaceDelim.Encode()),
+			schema: openapi3.NewObjectSchema().
+				WithProperty("a", openapi3.NewStringSchema()).
+				WithProperty("b", openapi3.NewIntegerSchema()).
+				WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())),
+			encoding: map[string]*openapi3.Encoding{
+				"c": &openapi3.Encoding{Style: openapi3.SerializationSpaceDelimited, Explode: boolPtr(false)},
+			},
+			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}},
+		},
+		{
+			name: "urlencoded pipe delimited",
+			mime: "application/x-www-form-urlencoded",
+			body: strings.NewReader(urlencodedPipeDelim.Encode()),
+			schema: openapi3.NewObjectSchema().
+				WithProperty("a", openapi3.NewStringSchema()).
+				WithProperty("b", openapi3.NewIntegerSchema()).
+				WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())),
+			encoding: map[string]*openapi3.Encoding{
+				"c": &openapi3.Encoding{Style: openapi3.SerializationPipeDelimited, Explode: boolPtr(false)},
+			},
+			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}},
+		},
+		{
+			name: "multipart",
+			mime: multipartFormMime,
+			body: multipartForm,
+			schema: openapi3.NewObjectSchema().
+				WithProperty("a", openapi3.NewStringSchema()).
+				WithProperty("b", openapi3.NewIntegerSchema()).
+				WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())).
+				WithProperty("d", openapi3.NewObjectSchema().WithProperty("d1", openapi3.NewStringSchema())).
+				WithProperty("f", openapi3.NewStringSchema().WithFormat("binary")),
+			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}, "d": map[string]interface{}{"d1": "d1"}, "f": "foo"},
+		},
+		{
+			name: "file",
+			mime: "application/octet-stream",
+			body: strings.NewReader("foo"),
 			want: "foo",
 		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			got, err := decodeBody(tc.body, tc.mime)
+			h := make(http.Header)
+			h.Set(http.CanonicalHeaderKey("Content-Type"), tc.mime)
+			var schemaRef *openapi3.SchemaRef
+			if tc.schema != nil {
+				schemaRef = tc.schema.NewRef()
+			}
+			encFn := func(name string) *openapi3.Encoding {
+				if tc.encoding == nil {
+					return nil
+				}
+				return tc.encoding[name]
+			}
+			got, err := decodeBody(tc.body, h, schemaRef, encFn)
 
 			if tc.wantErr != nil {
 				require.Error(t, err)
@@ -904,29 +1016,72 @@ func TestDecodeBody(t *testing.T) {
 	}
 }
 
+type testFormPart struct {
+	name        string
+	contentType string
+	data        io.Reader
+	filename    string
+}
+
+func newTestMultipartForm(parts []*testFormPart) (io.Reader, string, error) {
+	form := &bytes.Buffer{}
+	w := multipart.NewWriter(form)
+	defer w.Close()
+
+	for _, p := range parts {
+		var disp string
+		if p.filename == "" {
+			disp = fmt.Sprintf(`form-data; name="%s"`, p.name)
+		} else {
+			disp = fmt.Sprintf(`form-data; name="%s"; filename="%s"`, p.name, p.filename)
+		}
+
+		h := make(textproto.MIMEHeader)
+		h.Set("Content-Type", p.contentType)
+		h.Set("Content-Disposition", disp)
+		pw, err := w.CreatePart(h)
+		if err != nil {
+			return nil, "", err
+		}
+		_, err = io.Copy(pw, p.data)
+		if err != nil {
+			return nil, "", err
+		}
+	}
+	return form, w.FormDataContentType(), nil
+}
+
 func TestRegisterAndUnregisterBodyDecoder(t *testing.T) {
 	var (
 		contentType = "text/csv"
-		decoder     = func(body []byte) (interface{}, error) {
+		decoder     = func(body io.Reader, h http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+			data, err := ioutil.ReadAll(body)
+			if err != nil {
+				return nil, err
+			}
 			var vv []interface{}
-			for _, v := range strings.Split(string(body), ",") {
+			for _, v := range strings.Split(string(data), ",") {
 				vv = append(vv, v)
 			}
 			return vv, nil
 		}
-		body    = []byte("foo,bar")
+		schema  = openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema()).NewRef()
+		encFn   = func(string) *openapi3.Encoding { return nil }
+		body    = strings.NewReader("foo,bar")
 		want    = []interface{}{"foo", "bar"}
 		wantErr = &ParseError{Kind: KindUnsupportedFormat}
 	)
+	h := make(http.Header)
+	h.Set(http.CanonicalHeaderKey("Content-Type"), contentType)
 
 	RegisterBodyDecoder(contentType, decoder)
-	got, err := decodeBody(body, contentType)
+	got, err := decodeBody(body, h, schema, encFn)
 
 	require.NoError(t, err)
 	require.Truef(t, reflect.DeepEqual(got, want), "got %v, want %v", got, want)
 
 	UnregisterBodyDecoder(contentType)
-	_, err = decodeBody(body, contentType)
+	_, err = decodeBody(body, h, schema, encFn)
 
 	require.Error(t, err)
 	require.Truef(t, matchParseError(err, wantErr), "got error:\n%v\nwant error:\n%v", err, wantErr)
@@ -947,7 +1102,7 @@ func matchParseError(got, want error) bool {
 	if !reflect.DeepEqual(wErr.Value, gErr.Value) {
 		return false
 	}
-	if !reflect.DeepEqual(wErr.Path, gErr.Path) {
+	if !reflect.DeepEqual(wErr.Path(), gErr.Path()) {
 		return false
 	}
 	if wErr.Cause != nil {