Repository: getkin/kin-openapi
Pull request #262 (2020-10-26 02:02:15)

Prevent a panic in the error encoder
The Parameter can be nil when the enum is set of one of the body fields.

fenollp: Thanks for catching this & going all the way to a patch.

Could you provide a test that would fail if it weren't for your PR, please?

Patch:
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index d06d838..fbf2b09 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -2,6 +2,7 @@
 package openapi2conv
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/url"
@@ -40,35 +41,40 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 		}
 	}
 
-	if paths := swagger.Paths; paths != nil {
-		resultPaths := make(map[string]*openapi3.PathItem, len(paths))
-		for path, pathItem := range paths {
-			r, err := ToV3PathItem(swagger, pathItem)
-			if err != nil {
-				return nil, err
-			}
-			resultPaths[path] = r
-		}
-		result.Paths = resultPaths
-	}
-
-	if parameters := swagger.Parameters; parameters != nil {
-		result.Components.Parameters = make(map[string]*openapi3.ParameterRef, len(parameters))
-		result.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef, len(parameters))
+	result.Components.Schemas = make(map[string]*openapi3.SchemaRef)
+	if parameters := swagger.Parameters; len(parameters) != 0 {
+		result.Components.Parameters = make(map[string]*openapi3.ParameterRef)
+		result.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef)
 		for k, parameter := range parameters {
-			v3Parameter, v3RequestBody, err := ToV3Parameter(parameter)
+			v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(&result.Components, parameter)
 			switch {
 			case err != nil:
 				return nil, err
 			case v3RequestBody != nil:
 				result.Components.RequestBodies[k] = v3RequestBody
+			case v3SchemaMap != nil:
+				for _, v3Schema := range v3SchemaMap {
+					result.Components.Schemas[k] = v3Schema
+				}
 			default:
 				result.Components.Parameters[k] = v3Parameter
 			}
 		}
 	}
 
-	if responses := swagger.Responses; responses != nil {
+	if paths := swagger.Paths; len(paths) != 0 {
+		resultPaths := make(map[string]*openapi3.PathItem, len(paths))
+		for path, pathItem := range paths {
+			r, err := ToV3PathItem(swagger, &result.Components, pathItem)
+			if err != nil {
+				return nil, err
+			}
+			resultPaths[path] = r
+		}
+		result.Paths = resultPaths
+	}
+
+	if responses := swagger.Responses; len(responses) != 0 {
 		result.Components.Responses = make(map[string]*openapi3.ResponseRef, len(responses))
 		for k, response := range responses {
 			r, err := ToV3Response(response)
@@ -79,9 +85,11 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 		}
 	}
 
-	result.Components.Schemas = ToV3Schemas(swagger.Definitions)
+	for key, schema := range ToV3Schemas(swagger.Definitions) {
+		result.Components.Schemas[key] = schema
+	}
 
-	if m := swagger.SecurityDefinitions; m != nil {
+	if m := swagger.SecurityDefinitions; len(m) != 0 {
 		resultSecuritySchemes := make(map[string]*openapi3.SecuritySchemeRef)
 		for k, v := range m {
 			r, err := ToV3SecurityScheme(v)
@@ -94,7 +102,6 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 	}
 
 	result.Security = ToV3SecurityRequirements(swagger.Security)
-
 	{
 		sl := openapi3.NewSwaggerLoader()
 		if err := sl.ResolveRefsIn(result, nil); err != nil {
@@ -104,25 +111,27 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 	return result, nil
 }
 
-func ToV3PathItem(swagger *openapi2.Swagger, pathItem *openapi2.PathItem) (*openapi3.PathItem, error) {
+func ToV3PathItem(swagger *openapi2.Swagger, components *openapi3.Components, pathItem *openapi2.PathItem) (*openapi3.PathItem, error) {
 	stripNonCustomExtensions(pathItem.Extensions)
 	result := &openapi3.PathItem{
 		ExtensionProps: pathItem.ExtensionProps,
 	}
 	for method, operation := range pathItem.Operations() {
-		resultOperation, err := ToV3Operation(swagger, pathItem, operation)
+		resultOperation, err := ToV3Operation(swagger, components, pathItem, operation)
 		if err != nil {
 			return nil, err
 		}
 		result.SetOperation(method, resultOperation)
 	}
 	for _, parameter := range pathItem.Parameters {
-		v3Parameter, v3RequestBody, err := ToV3Parameter(parameter)
+		v3Parameter, v3RequestBody, v3Schema, err := ToV3Parameter(components, parameter)
 		switch {
 		case err != nil:
 			return nil, err
 		case v3RequestBody != nil:
 			return nil, errors.New("pathItem must not have a body parameter")
+		case v3Schema != nil:
+			return nil, errors.New("pathItem must not have a schema parameter")
 		default:
 			result.Parameters = append(result.Parameters, v3Parameter)
 		}
@@ -130,7 +139,7 @@ func ToV3PathItem(swagger *openapi2.Swagger, pathItem *openapi2.PathItem) (*open
 	return result, nil
 }
 
-func ToV3Operation(swagger *openapi2.Swagger, pathItem *openapi2.PathItem, operation *openapi2.Operation) (*openapi3.Operation, error) {
+func ToV3Operation(swagger *openapi2.Swagger, components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation) (*openapi3.Operation, error) {
 	if operation == nil {
 		return nil, nil
 	}
@@ -148,19 +157,24 @@ func ToV3Operation(swagger *openapi2.Swagger, pathItem *openapi2.PathItem, opera
 	}
 
 	var reqBodies []*openapi3.RequestBodyRef
+	formDataSchemas := make(map[string]*openapi3.SchemaRef)
 	for _, parameter := range operation.Parameters {
-		v3Parameter, v3RequestBody, err := ToV3Parameter(parameter)
+		v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(components, parameter)
 		switch {
 		case err != nil:
 			return nil, err
 		case v3RequestBody != nil:
 			reqBodies = append(reqBodies, v3RequestBody)
+		case v3SchemaMap != nil:
+			for key, v3Schema := range v3SchemaMap {
+				formDataSchemas[key] = v3Schema
+			}
 		default:
 			result.Parameters = append(result.Parameters, v3Parameter)
 		}
 	}
 	var err error
-	if result.RequestBody, err = onlyOneReqBodyParam(reqBodies); err != nil {
+	if result.RequestBody, err = onlyOneReqBodyParam(reqBodies, formDataSchemas, components); err != nil {
 		return nil, err
 	}
 
@@ -178,9 +192,31 @@ func ToV3Operation(swagger *openapi2.Swagger, pathItem *openapi2.PathItem, opera
 	return result, nil
 }
 
-func ToV3Parameter(parameter *openapi2.Parameter) (*openapi3.ParameterRef, *openapi3.RequestBodyRef, error) {
+func getParameterNameFromOldRef(ref string) string {
+	cleanPath := strings.TrimPrefix(ref, "#/parameters/")
+	pathSections := strings.SplitN(cleanPath, "/", 1)
+
+	return pathSections[0]
+}
+
+func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Parameter) (*openapi3.ParameterRef, *openapi3.RequestBodyRef, map[string]*openapi3.SchemaRef, error) {
 	if ref := parameter.Ref; ref != "" {
-		return &openapi3.ParameterRef{Ref: ToV3Ref(ref)}, nil, nil
+		if strings.HasPrefix(ref, "#/parameters/") {
+			name := getParameterNameFromOldRef(ref)
+			if _, ok := components.RequestBodies[name]; ok {
+				v3Ref := strings.Replace(ref, "#/parameters/", "#/components/requestBodies/", 1)
+				return nil, &openapi3.RequestBodyRef{Ref: v3Ref}, nil, nil
+			} else if schema, ok := components.Schemas[name]; ok {
+				schemaRefMap := make(map[string]*openapi3.SchemaRef)
+				if val, ok := schema.Value.Extensions["x-formData-name"]; ok {
+					name = val.(string)
+				}
+				v3Ref := strings.Replace(ref, "#/parameters/", "#/components/schemas/", 1)
+				schemaRefMap[name] = &openapi3.SchemaRef{Ref: v3Ref}
+				return nil, nil, schemaRefMap, nil
+			}
+		}
+		return &openapi3.ParameterRef{Ref: ToV3Ref(ref)}, nil, nil, nil
 	}
 	stripNonCustomExtensions(parameter.Extensions)
 
@@ -191,52 +227,63 @@ func ToV3Parameter(parameter *openapi2.Parameter) (*openapi3.ParameterRef, *open
 			Required:       parameter.Required,
 			ExtensionProps: parameter.ExtensionProps,
 		}
+		if parameter.Name != "" {
+			result.Extensions["x-originalParamName"] = parameter.Name
+		}
+
 		if schemaRef := parameter.Schema; schemaRef != nil {
 			// Assuming JSON
 			result.WithJSONSchemaRef(ToV3SchemaRef(schemaRef))
 		}
-		return nil, &openapi3.RequestBodyRef{Value: result}, nil
+		return nil, &openapi3.RequestBodyRef{Value: result}, nil, nil
 
 	case "formData":
 		format, typ := parameter.Format, parameter.Type
 		if typ == "file" {
 			format, typ = "binary", "string"
 		}
-		reqBodyRef := formDataBody(
-			map[string]*openapi3.SchemaRef{
-				parameter.Name: {
-					Value: &openapi3.Schema{
-						Description:     parameter.Description,
-						Type:            typ,
-						ExtensionProps:  parameter.ExtensionProps,
-						Format:          format,
-						Enum:            parameter.Enum,
-						Min:             parameter.Minimum,
-						Max:             parameter.Maximum,
-						ExclusiveMin:    parameter.ExclusiveMin,
-						ExclusiveMax:    parameter.ExclusiveMax,
-						MinLength:       parameter.MinLength,
-						MaxLength:       parameter.MaxLength,
-						Default:         parameter.Default,
-						Items:           parameter.Items,
-						MinItems:        parameter.MinItems,
-						MaxItems:        parameter.MaxItems,
-						Pattern:         parameter.Pattern,
-						AllowEmptyValue: parameter.AllowEmptyValue,
-						UniqueItems:     parameter.UniqueItems,
-						MultipleOf:      parameter.MultipleOf,
-					},
-				},
+		if parameter.ExtensionProps.Extensions == nil {
+			parameter.ExtensionProps.Extensions = make(map[string]interface{})
+		}
+		parameter.ExtensionProps.Extensions["x-formData-name"] = parameter.Name
+		var required []string
+		if parameter.Required {
+			required = []string{parameter.Name}
+		}
+		schemaRef := &openapi3.SchemaRef{
+			Value: &openapi3.Schema{
+				Description:     parameter.Description,
+				Type:            typ,
+				ExtensionProps:  parameter.ExtensionProps,
+				Format:          format,
+				Enum:            parameter.Enum,
+				Min:             parameter.Minimum,
+				Max:             parameter.Maximum,
+				ExclusiveMin:    parameter.ExclusiveMin,
+				ExclusiveMax:    parameter.ExclusiveMax,
+				MinLength:       parameter.MinLength,
+				MaxLength:       parameter.MaxLength,
+				Default:         parameter.Default,
+				Items:           parameter.Items,
+				MinItems:        parameter.MinItems,
+				MaxItems:        parameter.MaxItems,
+				Pattern:         parameter.Pattern,
+				AllowEmptyValue: parameter.AllowEmptyValue,
+				UniqueItems:     parameter.UniqueItems,
+				MultipleOf:      parameter.MultipleOf,
+				Required:        required,
 			},
-			map[string]bool{parameter.Name: parameter.Required},
-		)
-		return nil, reqBodyRef, nil
+		}
+		schemaRefMap := make(map[string]*openapi3.SchemaRef)
+		schemaRefMap[parameter.Name] = schemaRef
+		return nil, nil, schemaRefMap, nil
 
 	default:
 		required := parameter.Required
 		if parameter.In == openapi3.ParameterInPath {
 			required = true
 		}
+
 		result := &openapi3.Parameter{
 			In:             parameter.In,
 			Name:           parameter.Name,
@@ -263,7 +310,7 @@ func ToV3Parameter(parameter *openapi2.Parameter) (*openapi3.ParameterRef, *open
 				MultipleOf:      parameter.MultipleOf,
 			}}),
 		}
-		return &openapi3.ParameterRef{Value: result}, nil, nil
+		return &openapi3.ParameterRef{Value: result}, nil, nil, nil
 	}
 }
 
@@ -290,43 +337,59 @@ func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool) *
 	}
 }
 
-func onlyOneReqBodyParam(bodies []*openapi3.RequestBodyRef) (*openapi3.RequestBodyRef, error) {
-	var (
-		body           *openapi3.RequestBodyRef
-		formDataParams map[string]*openapi3.SchemaRef
-		formDataReqs   map[string]bool
-	)
-	for i, requestBodyRef := range bodies {
-		mediaType := requestBodyRef.Value.GetMediaType("multipart/form-data")
-		if mediaType != nil {
-			for name, schemaRef := range mediaType.Schema.Value.Properties {
-				if formDataParams == nil {
-					formDataParams = make(map[string]*openapi3.SchemaRef, len(bodies)-i)
-				}
-				if formDataReqs == nil {
-					formDataReqs = make(map[string]bool, len(bodies)-i)
+func getParameterNameFromNewRef(ref string) string {
+	cleanPath := strings.TrimPrefix(ref, "#/components/schemas/")
+	pathSections := strings.SplitN(cleanPath, "/", 1)
+
+	return pathSections[0]
+}
+
+func onlyOneReqBodyParam(bodies []*openapi3.RequestBodyRef, formDataSchemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (*openapi3.RequestBodyRef, error) {
+	if len(bodies) > 1 {
+		return nil, errors.New("multiple body parameters cannot exist for the same operation")
+	}
+
+	if len(bodies) != 0 && len(formDataSchemas) != 0 {
+		return nil, errors.New("body and form parameters cannot exist together for the same operation")
+	}
+
+	for _, requestBodyRef := range bodies {
+		return requestBodyRef, nil
+	}
+
+	if len(formDataSchemas) > 0 {
+		formDataParams := make(map[string]*openapi3.SchemaRef, len(formDataSchemas))
+		formDataReqs := make(map[string]bool, len(formDataSchemas))
+		for formDataName, formDataSchema := range formDataSchemas {
+			if formDataSchema.Ref != "" {
+				name := getParameterNameFromNewRef(formDataSchema.Ref)
+				if schema := components.Schemas[name]; schema != nil && schema.Value != nil {
+					if tempName, ok := schema.Value.Extensions["x-formData-name"]; ok {
+						name = tempName.(string)
+					}
+					formDataParams[name] = formDataSchema
+					formDataReqs[name] = false
+					for _, req := range schema.Value.Required {
+						if name == req {
+							formDataReqs[name] = true
+						}
+					}
 				}
-				formDataParams[name] = schemaRef
-				formDataReqs[name] = false
-				for _, req := range mediaType.Schema.Value.Required {
-					if name == req {
-						formDataReqs[name] = true
+			} else if formDataSchema.Value != nil {
+				formDataParams[formDataName] = formDataSchema
+				formDataReqs[formDataName] = false
+				for _, req := range formDataSchema.Value.Required {
+					if formDataName == req {
+						formDataReqs[formDataName] = true
 					}
 				}
-				break
 			}
-		} else {
-			body = requestBodyRef
 		}
-	}
-	switch {
-	case len(formDataParams) != 0 && body != nil:
-		return nil, errors.New("body and form parameters cannot exist together for the same operation")
-	case len(formDataParams) != 0:
+
 		return formDataBody(formDataParams, formDataReqs), nil
-	default:
-		return body, nil
 	}
+
+	return nil, nil
 }
 
 func ToV3Response(response *openapi2.Response) (*openapi3.ResponseRef, error) {
@@ -395,6 +458,8 @@ func FromV3Ref(ref string) string {
 	for new, old := range ref2To3 {
 		if strings.HasPrefix(ref, old) {
 			ref = strings.Replace(ref, old, new, 1)
+		} else if strings.HasPrefix(ref, "#/components/requestBodies/") {
+			ref = strings.Replace(ref, "#/components/requestBodies/", "#/parameters/", 1)
 		}
 	}
 	return ref
@@ -460,16 +525,17 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
 
 // FromV3Swagger converts an OpenAPIv3 spec to an OpenAPIv2 spec
 func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
-	resultResponses, err := FromV3Responses(swagger.Components.Responses)
+	resultResponses, err := FromV3Responses(swagger.Components.Responses, &swagger.Components)
 	if err != nil {
 		return nil, err
 	}
 	stripNonCustomExtensions(swagger.Extensions)
-
+	schemas, parameters := FromV3Schemas(swagger.Components.Schemas, &swagger.Components)
 	result := &openapi2.Swagger{
 		Swagger:        "2.0",
 		Info:           *swagger.Info,
-		Definitions:    FromV3Schemas(swagger.Components.Schemas),
+		Definitions:    schemas,
+		Parameters:     parameters,
 		Responses:      resultResponses,
 		Tags:           swagger.Tags,
 		ExtensionProps: swagger.ExtensionProps,
@@ -520,7 +586,7 @@ func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
 		}
 		params := openapi2.Parameters{}
 		for _, param := range pathItem.Parameters {
-			p, err := FromV3Parameter(param)
+			p, err := FromV3Parameter(param, &swagger.Components)
 			if err != nil {
 				return nil, err
 			}
@@ -528,12 +594,35 @@ func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
 		}
 		result.Paths[path].Parameters = params
 	}
-	result.Parameters = map[string]*openapi2.Parameter{}
+
 	for name, param := range swagger.Components.Parameters {
-		if result.Parameters[name], err = FromV3Parameter(param); err != nil {
+		if result.Parameters[name], err = FromV3Parameter(param, &swagger.Components); err != nil {
 			return nil, err
 		}
 	}
+
+	for name, requestBody := range swagger.Components.RequestBodies {
+		parameters := FromV3RequestBodyFormData(requestBody)
+		for _, param := range parameters {
+			result.Parameters[param.Name] = param
+		}
+
+		if len(parameters) == 0 {
+			paramName := name
+			if requestBody.Value != nil {
+				if originalName, ok := requestBody.Value.Extensions["x-originalParamName"]; ok {
+					json.Unmarshal(originalName.(json.RawMessage), &paramName)
+				}
+			}
+
+			r, err := FromV3RequestBody(swagger, paramName, requestBody)
+			if err != nil {
+				return nil, err
+			}
+			result.Parameters[name] = r
+		}
+	}
+
 	if m := swagger.Components.SecuritySchemes; m != nil {
 		resultSecuritySchemes := make(map[string]*openapi2.SecurityScheme)
 		for id, securityScheme := range m {
@@ -549,34 +638,89 @@ func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
 	return result, nil
 }
 
-func FromV3Schemas(schemas map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef {
-	v2Defs := make(map[string]*openapi3.SchemaRef, len(schemas))
+func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter) {
+	v2Defs := make(map[string]*openapi3.SchemaRef)
+	v2Params := make(map[string]*openapi2.Parameter)
 	for name, schema := range schemas {
-		v2Defs[name] = FromV3SchemaRef(schema)
+		schemaConv, parameterConv := FromV3SchemaRef(schema, components)
+		if schemaConv != nil {
+			v2Defs[name] = schemaConv
+		} else if parameterConv != nil {
+			if parameterConv.Name == "" {
+				parameterConv.Name = name
+			}
+			v2Params[name] = parameterConv
+		}
 	}
-	return v2Defs
+	return v2Defs, v2Params
 }
 
-func FromV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
+func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter) {
 	if ref := schema.Ref; ref != "" {
-		return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}
+		name := getParameterNameFromNewRef(ref)
+		if val, ok := components.Schemas[name]; ok {
+			if val.Value.Format == "binary" {
+				v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1)
+				return nil, &openapi2.Parameter{Ref: v2Ref}
+			}
+		}
+
+		return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}, nil
 	}
 	if schema.Value == nil {
-		return schema
+		return schema, nil
+	}
+
+	if schema.Value != nil {
+		if schema.Value.Type == "string" && schema.Value.Format == "binary" {
+			paramType := "file"
+			required := false
+
+			value, _ := schema.Value.Extensions["x-formData-name"]
+			var originalName string
+			json.Unmarshal(value.(json.RawMessage), &originalName)
+			for _, prop := range schema.Value.Required {
+				if originalName == prop {
+					required = true
+				}
+			}
+			return nil, &openapi2.Parameter{
+				In:              "formData",
+				Name:            originalName,
+				Description:     schema.Value.Description,
+				Type:            paramType,
+				Enum:            schema.Value.Enum,
+				Minimum:         schema.Value.Min,
+				Maximum:         schema.Value.Max,
+				ExclusiveMin:    schema.Value.ExclusiveMin,
+				ExclusiveMax:    schema.Value.ExclusiveMax,
+				MinLength:       schema.Value.MinLength,
+				MaxLength:       schema.Value.MaxLength,
+				Default:         schema.Value.Default,
+				Items:           schema.Value.Items,
+				MinItems:        schema.Value.MinItems,
+				MaxItems:        schema.Value.MaxItems,
+				AllowEmptyValue: schema.Value.AllowEmptyValue,
+				UniqueItems:     schema.Value.UniqueItems,
+				MultipleOf:      schema.Value.MultipleOf,
+				ExtensionProps:  schema.Value.ExtensionProps,
+				Required:        required,
+			}
+		}
 	}
 	if v := schema.Value.Items; v != nil {
-		schema.Value.Items = FromV3SchemaRef(v)
+		schema.Value.Items, _ = FromV3SchemaRef(v, components)
 	}
 	for k, v := range schema.Value.Properties {
-		schema.Value.Properties[k] = FromV3SchemaRef(v)
+		schema.Value.Properties[k], _ = FromV3SchemaRef(v, components)
 	}
 	if v := schema.Value.AdditionalProperties; v != nil {
-		schema.Value.AdditionalProperties = FromV3SchemaRef(v)
+		schema.Value.AdditionalProperties, _ = FromV3SchemaRef(v, components)
 	}
 	for i, v := range schema.Value.AllOf {
-		schema.Value.AllOf[i] = FromV3SchemaRef(v)
+		schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
 	}
-	return schema
+	return schema, nil
 }
 
 func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements {
@@ -603,7 +747,7 @@ func FromV3PathItem(swagger *openapi3.Swagger, pathItem *openapi3.PathItem) (*op
 		result.SetOperation(method, r)
 	}
 	for _, parameter := range pathItem.Parameters {
-		p, err := FromV3Parameter(parameter)
+		p, err := FromV3Parameter(parameter, &swagger.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -633,6 +777,11 @@ func FromV3RequestBodyFormData(requestBodyRef *openapi3.RequestBodyRef) openapi2
 	}
 	parameters := openapi2.Parameters{}
 	for propName, schemaRef := range mediaType.Schema.Value.Properties {
+		if ref := schemaRef.Ref; ref != "" {
+			v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1)
+			parameters = append(parameters, &openapi2.Parameter{Ref: v2Ref})
+			continue
+		}
 		val := schemaRef.Value
 		typ := val.Type
 		if val.Format == "binary" {
@@ -692,7 +841,7 @@ func FromV3Operation(swagger *openapi3.Swagger, operation *openapi3.Operation) (
 		result.Security = &resultSecurity
 	}
 	for _, parameter := range operation.Parameters {
-		r, err := FromV3Parameter(parameter)
+		r, err := FromV3Parameter(parameter, &swagger.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -703,20 +852,28 @@ func FromV3Operation(swagger *openapi3.Swagger, operation *openapi3.Operation) (
 		if len(parameters) > 0 {
 			result.Parameters = append(result.Parameters, parameters...)
 		} else {
-			r, err := FromV3RequestBody(swagger, operation, v)
+			// Find parameter name that we can use for the body
+			name := findNameForRequestBody(operation)
+			if name == "" {
+				return nil, errors.New("could not find a name for request body")
+			}
+			r, err := FromV3RequestBody(swagger, name, v)
 			if err != nil {
 				return nil, err
 			}
 			result.Parameters = append(result.Parameters, r)
 		}
 	}
+
 	for _, param := range result.Parameters {
 		if param.Type == "file" {
 			result.Consumes = append(result.Consumes, "multipart/form-data")
+			break
 		}
 	}
+
 	if responses := operation.Responses; responses != nil {
-		resultResponses, err := FromV3Responses(responses)
+		resultResponses, err := FromV3Responses(responses, &swagger.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -725,19 +882,12 @@ func FromV3Operation(swagger *openapi3.Swagger, operation *openapi3.Operation) (
 	return result, nil
 }
 
-func FromV3RequestBody(swagger *openapi3.Swagger, operation *openapi3.Operation, requestBodyRef *openapi3.RequestBodyRef) (*openapi2.Parameter, error) {
+func FromV3RequestBody(swagger *openapi3.Swagger, name string, requestBodyRef *openapi3.RequestBodyRef) (*openapi2.Parameter, error) {
 	if ref := requestBodyRef.Ref; ref != "" {
 		return &openapi2.Parameter{Ref: FromV3Ref(ref)}, nil
 	}
 	requestBody := requestBodyRef.Value
 
-	// Find parameter name that we can use for the body
-	name := findNameForRequestBody(operation)
-
-	// If found an available name
-	if name == "" {
-		return nil, errors.New("Could not find a name for request body")
-	}
 	stripNonCustomExtensions(requestBody.Extensions)
 	result := &openapi2.Parameter{
 		In:             "body",
@@ -750,12 +900,12 @@ func FromV3RequestBody(swagger *openapi3.Swagger, operation *openapi3.Operation,
 	// Assuming JSON
 	mediaType := requestBody.GetMediaType("application/json")
 	if mediaType != nil {
-		result.Schema = FromV3SchemaRef(mediaType.Schema)
+		result.Schema, _ = FromV3SchemaRef(mediaType.Schema, &swagger.Components)
 	}
 	return result, nil
 }
 
-func FromV3Parameter(ref *openapi3.ParameterRef) (*openapi2.Parameter, error) {
+func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components) (*openapi2.Parameter, error) {
 	if ref := ref.Ref; ref != "" {
 		return &openapi2.Parameter{Ref: FromV3Ref(ref)}, nil
 	}
@@ -772,7 +922,7 @@ func FromV3Parameter(ref *openapi3.ParameterRef) (*openapi2.Parameter, error) {
 		ExtensionProps: parameter.ExtensionProps,
 	}
 	if schemaRef := parameter.Schema; schemaRef != nil {
-		schemaRef = FromV3SchemaRef(schemaRef)
+		schemaRef, _ = FromV3SchemaRef(schemaRef, components)
 		schema := schemaRef.Value
 		result.Type = schema.Type
 		result.Format = schema.Format
@@ -796,10 +946,10 @@ func FromV3Parameter(ref *openapi3.ParameterRef) (*openapi2.Parameter, error) {
 	return result, nil
 }
 
-func FromV3Responses(responses map[string]*openapi3.ResponseRef) (map[string]*openapi2.Response, error) {
+func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *openapi3.Components) (map[string]*openapi2.Response, error) {
 	v2Responses := make(map[string]*openapi2.Response, len(responses))
 	for k, response := range responses {
-		r, err := FromV3Response(response)
+		r, err := FromV3Response(response, components)
 		if err != nil {
 			return nil, err
 		}
@@ -808,7 +958,7 @@ func FromV3Responses(responses map[string]*openapi3.ResponseRef) (map[string]*op
 	return v2Responses, nil
 }
 
-func FromV3Response(ref *openapi3.ResponseRef) (*openapi2.Response, error) {
+func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components) (*openapi2.Response, error) {
 	if ref := ref.Ref; ref != "" {
 		return &openapi2.Response{Ref: FromV3Ref(ref)}, nil
 	}
@@ -828,7 +978,7 @@ func FromV3Response(ref *openapi3.ResponseRef) (*openapi2.Response, error) {
 	}
 	if content := response.Content; content != nil {
 		if ct := content["application/json"]; ct != nil {
-			result.Schema = FromV3SchemaRef(ct.Schema)
+			result.Schema, _ = FromV3SchemaRef(ct.Schema, components)
 		}
 	}
 	return result, nil
diff --git a/openapi3/errors.go b/openapi3/errors.go
new file mode 100644
index 0000000..ce52cd4
--- /dev/null
+++ b/openapi3/errors.go
@@ -0,0 +1,43 @@
+package openapi3
+
+import (
+	"bytes"
+	"errors"
+)
+
+// MultiError is a collection of errors, intended for when
+// multiple issues need to be reported upstream
+type MultiError []error
+
+func (me MultiError) Error() string {
+	buff := &bytes.Buffer{}
+	for _, e := range me {
+		buff.WriteString(e.Error())
+		buff.WriteString(" | ")
+	}
+	return buff.String()
+}
+
+//Is allows you to determine if a generic error is in fact a MultiError using `errors.Is()`
+//It will also return true if any of the contained errors match target
+func (me MultiError) Is(target error) bool {
+	if _, ok := target.(MultiError); ok {
+		return true
+	}
+	for _, e := range me {
+		if errors.Is(e, target) {
+			return true
+		}
+	}
+	return false
+}
+
+//As allows you to use `errors.As()` to set target to the first error within the multi error that matches the target type
+func (me MultiError) As(target interface{}) bool {
+	for _, e := range me {
+		if errors.As(e, target) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/openapi3/schema.go b/openapi3/schema.go
index a4e3b8d..41d6fd9 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -802,19 +802,24 @@ func (schema *Schema) VisitJSONNumber(value float64) error {
 	return schema.visitJSONNumber(settings, value)
 }
 
-func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) (err error) {
+func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) error {
+	var me MultiError
 	schemaType := schema.Type
 	if schemaType == "integer" {
 		if bigFloat := big.NewFloat(value); !bigFloat.IsInt() {
 			if settings.failfast {
 				return errSchema
 			}
-			return &SchemaError{
+			err := &SchemaError{
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "type",
 				Reason:      "Value must be an integer",
 			}
+			if !settings.multiError {
+				return err
+			}
+			me = append(me, err)
 		}
 	} else if schemaType != "" && schemaType != "number" {
 		return schema.expectedType(settings, "number, integer")
@@ -825,12 +830,16 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "exclusiveMinimum",
 			Reason:      fmt.Sprintf("Number must be more than %g", *schema.Min),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "exclusiveMaximum"
@@ -838,12 +847,16 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "exclusiveMaximum",
 			Reason:      fmt.Sprintf("Number must be less than %g", *schema.Max),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "minimum"
@@ -851,12 +864,16 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "minimum",
 			Reason:      fmt.Sprintf("Number must be at least %g", *v),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "maximum"
@@ -864,12 +881,16 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "maximum",
 			Reason:      fmt.Sprintf("Number must be most %g", *v),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "multipleOf"
@@ -880,14 +901,23 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			if settings.failfast {
 				return errSchema
 			}
-			return &SchemaError{
+			err := &SchemaError{
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "multipleOf",
 			}
+			if !settings.multiError {
+				return err
+			}
+			me = append(me, err)
 		}
 	}
-	return
+
+	if len(me) > 0 {
+		return me
+	}
+
+	return nil
 }
 
 func (schema *Schema) VisitJSONString(value string) error {
@@ -895,11 +925,13 @@ func (schema *Schema) VisitJSONString(value string) error {
 	return schema.visitJSONString(settings, value)
 }
 
-func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) (err error) {
+func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error {
 	if schemaType := schema.Type; schemaType != "" && schemaType != "string" {
 		return schema.expectedType(settings, "string")
 	}
 
+	var me MultiError
+
 	// "minLength" and "maxLength"
 	minLength := schema.MinLength
 	maxLength := schema.MaxLength
@@ -917,23 +949,31 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 			if settings.failfast {
 				return errSchema
 			}
-			return &SchemaError{
+			err := &SchemaError{
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "minLength",
 				Reason:      fmt.Sprintf("Minimum string length is %d", minLength),
 			}
+			if !settings.multiError {
+				return err
+			}
+			me = append(me, err)
 		}
 		if maxLength != nil && length > int64(*maxLength) {
 			if settings.failfast {
 				return errSchema
 			}
-			return &SchemaError{
+			err := &SchemaError{
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "maxLength",
 				Reason:      fmt.Sprintf("Maximum string length is %d", *maxLength),
 			}
+			if !settings.multiError {
+				return err
+			}
+			me = append(me, err)
 		}
 	}
 
@@ -970,15 +1010,24 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 			if schema.Pattern != "" {
 				field = "pattern"
 			}
-			return &SchemaError{
+			err := &SchemaError{
 				Value:       value,
 				Schema:      schema,
 				SchemaField: field,
 				Reason:      cp.ErrReason,
 			}
+			if !settings.multiError {
+				return err
+			}
+			me = append(me, err)
 		}
 	}
-	return
+
+	if len(me) > 0 {
+		return me
+	}
+
+	return nil
 }
 
 func (schema *Schema) VisitJSONArray(value []interface{}) error {
@@ -986,11 +1035,13 @@ func (schema *Schema) VisitJSONArray(value []interface{}) error {
 	return schema.visitJSONArray(settings, value)
 }
 
-func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) (err error) {
+func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) error {
 	if schemaType := schema.Type; schemaType != "" && schemaType != "array" {
 		return schema.expectedType(settings, "array")
 	}
 
+	var me MultiError
+
 	lenValue := int64(len(value))
 
 	// "minItems"
@@ -998,12 +1049,16 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "minItems",
 			Reason:      fmt.Sprintf("Minimum number of items is %d", v),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "maxItems"
@@ -1011,12 +1066,16 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "maxItems",
 			Reason:      fmt.Sprintf("Maximum number of items is %d", *v),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "uniqueItems"
@@ -1027,12 +1086,16 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "uniqueItems",
 			Reason:      fmt.Sprintf("Duplicate items found"),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "items"
@@ -1042,12 +1105,25 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			return foundUnresolvedRef(itemSchemaRef.Ref)
 		}
 		for i, item := range value {
-			if err := itemSchema.VisitJSON(item); err != nil {
-				return markSchemaErrorIndex(err, i)
+			if err := itemSchema.visitJSON(settings, item); err != nil {
+				err = markSchemaErrorIndex(err, i)
+				if !settings.multiError {
+					return err
+				}
+				if itemMe, ok := err.(MultiError); ok {
+					me = append(me, itemMe...)
+				} else {
+					me = append(me, err)
+				}
 			}
 		}
 	}
-	return
+
+	if len(me) > 0 {
+		return me
+	}
+
+	return nil
 }
 
 func (schema *Schema) VisitJSONObject(value map[string]interface{}) error {
@@ -1055,11 +1131,13 @@ func (schema *Schema) VisitJSONObject(value map[string]interface{}) error {
 	return schema.visitJSONObject(settings, value)
 }
 
-func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]interface{}) (err error) {
+func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]interface{}) error {
 	if schemaType := schema.Type; schemaType != "" && schemaType != "object" {
 		return schema.expectedType(settings, "object")
 	}
 
+	var me MultiError
+
 	// "properties"
 	properties := schema.Properties
 	lenValue := int64(len(value))
@@ -1069,12 +1147,16 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "minProperties",
 			Reason:      fmt.Sprintf("There must be at least %d properties", v),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "maxProperties"
@@ -1082,12 +1164,16 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "maxProperties",
 			Reason:      fmt.Sprintf("There must be at most %d properties", *v),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "additionalProperties"
@@ -1103,11 +1189,19 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 				if p == nil {
 					return foundUnresolvedRef(propertyRef.Ref)
 				}
-				if err := p.VisitJSON(v); err != nil {
+				if err := p.visitJSON(settings, v); err != nil {
 					if settings.failfast {
 						return errSchema
 					}
-					return markSchemaErrorKey(err, k)
+					err = markSchemaErrorKey(err, k)
+					if !settings.multiError {
+						return err
+					}
+					if v, ok := err.(MultiError); ok {
+						me = append(me, v...)
+						continue
+					}
+					me = append(me, err)
 				}
 				continue
 			}
@@ -1115,11 +1209,19 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 		allowed := schema.AdditionalPropertiesAllowed
 		if additionalProperties != nil || allowed == nil || (allowed != nil && *allowed) {
 			if additionalProperties != nil {
-				if err := additionalProperties.VisitJSON(v); err != nil {
+				if err := additionalProperties.visitJSON(settings, v); err != nil {
 					if settings.failfast {
 						return errSchema
 					}
-					return markSchemaErrorKey(err, k)
+					err = markSchemaErrorKey(err, k)
+					if !settings.multiError {
+						return err
+					}
+					if v, ok := err.(MultiError); ok {
+						me = append(me, v...)
+						continue
+					}
+					me = append(me, err)
 				}
 			}
 			continue
@@ -1127,12 +1229,16 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 		if settings.failfast {
 			return errSchema
 		}
-		return &SchemaError{
+		err := &SchemaError{
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "properties",
 			Reason:      fmt.Sprintf("Property '%s' is unsupported", k),
 		}
+		if !settings.multiError {
+			return err
+		}
+		me = append(me, err)
 	}
 
 	// "required"
@@ -1147,15 +1253,24 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			if settings.failfast {
 				return errSchema
 			}
-			return markSchemaErrorKey(&SchemaError{
+			err := markSchemaErrorKey(&SchemaError{
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "required",
 				Reason:      fmt.Sprintf("Property '%s' is missing", k),
 			}, k)
+			if !settings.multiError {
+				return err
+			}
+			me = append(me, err)
 		}
 	}
-	return
+
+	if len(me) > 0 {
+		return me
+	}
+
+	return nil
 }
 
 func (schema *Schema) expectedType(settings *schemaValidationSettings, typ string) error {
@@ -1184,6 +1299,12 @@ func markSchemaErrorKey(err error, key string) error {
 		v.reversePath = append(v.reversePath, key)
 		return v
 	}
+	if v, ok := err.(MultiError); ok {
+		for _, e := range v {
+			_ = markSchemaErrorKey(e, key)
+		}
+		return v
+	}
 	return err
 }
 
@@ -1192,6 +1313,12 @@ func markSchemaErrorIndex(err error, index int) error {
 		v.reversePath = append(v.reversePath, strconv.FormatInt(int64(index), 10))
 		return v
 	}
+	if v, ok := err.(MultiError); ok {
+		for _, e := range v {
+			_ = markSchemaErrorIndex(e, index)
+		}
+		return v
+	}
 	return err
 }
 
diff --git a/openapi3/schema_validation_settings.go b/openapi3/schema_validation_settings.go
index 6c073cd..71db5f2 100644
--- a/openapi3/schema_validation_settings.go
+++ b/openapi3/schema_validation_settings.go
@@ -5,6 +5,7 @@ type SchemaValidationOption func(*schemaValidationSettings)
 
 type schemaValidationSettings struct {
 	failfast     bool
+	multiError   bool
 	asreq, asrep bool // exclusive (XOR) fields
 }
 
@@ -13,6 +14,10 @@ func FailFast() SchemaValidationOption {
 	return func(s *schemaValidationSettings) { s.failfast = true }
 }
 
+func MultiErrors() SchemaValidationOption {
+	return func(s *schemaValidationSettings) { s.multiError = true }
+}
+
 func VisitAsRequest() SchemaValidationOption {
 	return func(s *schemaValidationSettings) { s.asreq, s.asrep = true, false }
 }
diff --git a/openapi3filter/fixtures/petstore.json b/openapi3filter/fixtures/petstore.json
index 6c229a6..398e9b8 100644
--- a/openapi3filter/fixtures/petstore.json
+++ b/openapi3filter/fixtures/petstore.json
@@ -67,7 +67,21 @@
         ],
         "requestBody": {
           "$ref": "#/components/requestBodies/PetWithRequired"
-        }
+        },
+        "parameters": [
+          {
+            "schema": {
+              "type": "string",
+              "enum": [
+                "demo",
+                "prod"
+              ]
+            },
+            "in": "header",
+            "name": "x-environment",
+            "description": "Where to send the data for processing"
+          }
+        ]
       },
       "patch": {
         "tags": [
@@ -1136,7 +1150,7 @@
           },
           "name": {
             "type": "string",
-            "example": "doggie",
+            "example": "doggie"
           },
           "photoUrls": {
             "type": "array",
@@ -1196,7 +1210,7 @@
           },
           "name": {
             "type": "string",
-            "example": "doggie",
+            "example": "doggie"
           },
           "photoUrls": {
             "type": "array",
@@ -1330,4 +1344,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/openapi3filter/options.go b/openapi3filter/options.go
index 510b777..60e5475 100644
--- a/openapi3filter/options.go
+++ b/openapi3filter/options.go
@@ -10,5 +10,6 @@ type Options struct {
 	ExcludeRequestBody    bool
 	ExcludeResponseBody   bool
 	IncludeResponseStatus bool
+	MultiError            bool
 	AuthenticationFunc    func(c context.Context, input *AuthenticationInput) error
 }
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 69fc58b..0af54c2 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -22,6 +22,11 @@ var ErrInvalidRequired = errors.New("must have a value")
 // Note: One can tune the behavior of uniqueItems: true verification
 // by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker
 func ValidateRequest(c context.Context, input *RequestValidationInput) error {
+	var (
+		err error
+		me  openapi3.MultiError
+	)
+
 	options := input.Options
 	if options == nil {
 		options = DefaultOptions
@@ -45,24 +50,37 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error {
 				continue
 			}
 		}
-		if err := ValidateParameter(c, input, parameter); err != nil {
+
+		if err = ValidateParameter(c, input, parameter); err != nil && !options.MultiError {
 			return err
 		}
+
+		if err != nil {
+			me = append(me, err)
+		}
 	}
 
 	// For each parameter of the Operation
 	for _, parameter := range operationParameters {
-		if err := ValidateParameter(c, input, parameter.Value); err != nil {
+		if err = ValidateParameter(c, input, parameter.Value); err != nil && !options.MultiError {
 			return err
 		}
+
+		if err != nil {
+			me = append(me, err)
+		}
 	}
 
 	// RequestBody
 	requestBody := operation.RequestBody
 	if requestBody != nil && !options.ExcludeRequestBody {
-		if err := ValidateRequestBody(c, input, requestBody.Value); err != nil {
+		if err = ValidateRequestBody(c, input, requestBody.Value); err != nil && !options.MultiError {
 			return err
 		}
+
+		if err != nil {
+			me = append(me, err)
+		}
 	}
 
 	// Security
@@ -76,10 +94,19 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error {
 		security = &route.Swagger.Security
 	}
 	if security != nil {
-		if err := ValidateSecurityRequirements(c, input, *security); err != nil {
+		if err = ValidateSecurityRequirements(c, input, *security); err != nil && !options.MultiError {
 			return err
 		}
+
+		if err != nil {
+			me = append(me, err)
+		}
 	}
+
+	if len(me) > 0 {
+		return me
+	}
+
 	return nil
 }
 
@@ -95,6 +122,11 @@ func ValidateParameter(c context.Context, input *RequestValidationInput, paramet
 		return nil
 	}
 
+	options := input.Options
+	if options == nil {
+		options = DefaultOptions
+	}
+
 	var value interface{}
 	var err error
 	var schema *openapi3.Schema
@@ -121,7 +153,13 @@ func ValidateParameter(c context.Context, input *RequestValidationInput, paramet
 		// A parameter's schema is not defined so skip validation of a parameter's value.
 		return nil
 	}
-	if err = schema.VisitJSON(value); err != nil {
+
+	var opts []openapi3.SchemaValidationOption
+	if options.MultiError {
+		opts = make([]openapi3.SchemaValidationOption, 0, 1)
+		opts = append(opts, openapi3.MultiErrors())
+	}
+	if err = schema.VisitJSON(value, opts...); err != nil {
 		return &RequestError{Input: input, Parameter: parameter, Err: err}
 	}
 	return nil
@@ -137,6 +175,11 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque
 		data []byte
 	)
 
+	options := input.Options
+	if options == nil {
+		options = DefaultOptions
+	}
+
 	if req.Body != http.NoBody && req.Body != nil {
 		defer req.Body.Close()
 		var err error
@@ -191,8 +234,14 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque
 		}
 	}
 
+	opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here
+	opts = append(opts, openapi3.VisitAsRequest())
+	if options.MultiError {
+		opts = append(opts, openapi3.MultiErrors())
+	}
+
 	// Validate JSON with the schema
-	if err := contentType.Schema.Value.VisitJSON(value, openapi3.VisitAsRequest()); err != nil {
+	if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
 		return &RequestError{
 			Input:       input,
 			RequestBody: requestBody,
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index 9a458aa..f203802 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -128,8 +128,14 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
 		}
 	}
 
+	opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here
+	opts = append(opts, openapi3.VisitAsRequest())
+	if options.MultiError {
+		opts = append(opts, openapi3.MultiErrors())
+	}
+
 	// Validate data with the schema.
-	if err := contentType.Schema.Value.VisitJSON(value, openapi3.VisitAsResponse()); err != nil {
+	if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
 		return &ResponseError{
 			Input:  input,
 			Reason: "response body doesn't match the schema",
diff --git a/openapi3filter/validation_error_encoder.go b/openapi3filter/validation_error_encoder.go
index 6bde134..34e4af9 100644
--- a/openapi3filter/validation_error_encoder.go
+++ b/openapi3filter/validation_error_encoder.go
@@ -148,7 +148,7 @@ func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *Valida
 	}
 
 	// Add error source
-	if e.Parameter != nil && e.Parameter.In == "query" {
+	if e.Parameter != nil {
 		// We have a JSONPointer in the query param too so need to
 		// make sure 'Parameter' check takes priority over 'Pointer'
 		cErr.Source = &ValidationErrorSource{
@@ -172,7 +172,8 @@ func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *Valida
 		cErr.Detail = fmt.Sprintf("Value '%v' at %s must be one of: %s",
 			innerErr.Value, toJSONPointer(innerErr.JSONPointer()), strings.Join(enums, ", "))
 		value := fmt.Sprintf("%v", innerErr.Value)
-		if (e.Parameter.Explode == nil || *e.Parameter.Explode == true) &&
+		if e.Parameter != nil &&
+			(e.Parameter.Explode == nil || *e.Parameter.Explode == true) &&
 			(e.Parameter.Style == "" || e.Parameter.Style == "form") &&
 			strings.Contains(value, ",") {
 			parts := strings.Split(value, ",")


Test patch:
diff --git a/openapi2conv/openapi2_conv_test.go b/openapi2conv/openapi2_conv_test.go
index 8ccc32c..c5379ae 100644
--- a/openapi2conv/openapi2_conv_test.go
+++ b/openapi2conv/openapi2_conv_test.go
@@ -94,6 +94,24 @@ const exampleV2 = `
       "name": "banana",
       "required": true,
       "type": "string"
+    },
+    "put_body": {
+      "in": "body",
+      "name": "banana",
+      "required": true,
+      "schema": {
+        "type": "string"
+      },
+      "x-originalParamName":"banana"
+    },
+    "post_form_ref": {
+      "required": true,
+      "description": "param description",
+      "in": "formData",
+      "name": "fileUpload2",
+      "type": "file",
+      "x-formData-name":"fileUpload2",
+      "x-mimetype": "text/plain"
     }
   },
   "paths": {
@@ -214,6 +232,7 @@ const exampleV2 = `
             "schema": {
               "allOf": [{"$ref": "#/definitions/Item"}]
             },
+            "x-originalParamName":"body",
             "x-requestBody": "requestbody extension 1"
           }
         ],
@@ -238,13 +257,18 @@ const exampleV2 = `
             "in": "formData",
             "name": "fileUpload",
             "type": "file",
+            "x-formData-name":"fileUpload", 
             "x-mimetype": "text/plain"
           },
           {
             "description": "Description of file contents",
             "in": "formData",
             "name": "note",
-            "type": "integer"
+            "type": "integer",
+            "x-formData-name":"note" 
+          },
+          {
+            "$ref": "#/parameters/post_form_ref"
           }
         ],
         "responses": {
@@ -255,6 +279,11 @@ const exampleV2 = `
       },
       "put": {
         "description": "example put",
+        "parameters": [
+          { 
+            "$ref": "#/parameters/put_body"
+          }
+        ],
         "responses": {
           "default": {
             "description": "default response"
@@ -309,6 +338,19 @@ const exampleV3 = `
         }
       }
     },
+    "requestBodies": {
+      "put_body": {
+        "content":{ 
+          "application/json": {
+            "schema": {
+              "type": "string"
+            }
+          }
+        },
+        "required": true,
+        "x-originalParamName":"banana"
+      }
+    },
     "responses": {
       "ForbiddenError": {
         "content": {
@@ -349,6 +391,14 @@ const exampleV3 = `
       "ItemExtension": {
         "type": "boolean",
         "description": "It could be anything."
+      },
+      "post_form_ref": {
+        "description": "param description",
+        "format": "binary",
+        "required": ["fileUpload2"],
+        "type": "string",
+        "x-formData-name": "fileUpload2",
+        "x-mimetype":"text/plain"
       }
     }
   },
@@ -491,6 +541,7 @@ const exampleV3 = `
               }
             }
           },
+          "x-originalParamName":"body",
           "x-requestBody": "requestbody extension 1"
         },
         "responses": {
@@ -520,13 +571,19 @@ const exampleV3 = `
                     "description": "param description",
                     "type": "string",
                     "format": "binary",
+                    "x-formData-name":"fileUpload",
                     "x-mimetype": "text/plain"
                   },
                   "note": {
                     "description": "Description of file contents",
-                    "type": "integer"
+                    "type": "integer",
+                    "x-formData-name": "note"
+                  }, 
+                  "fileUpload2": {
+                    "$ref": "#/components/schemas/post_form_ref"
                   }
                 },
+                "required": ["fileUpload2"],
                 "type": "object"
               }
             }
@@ -540,6 +597,9 @@ const exampleV3 = `
       },
       "put": {
         "description": "example put",
+        "requestBody": {
+          "$ref": "#/components/requestBodies/put_body"
+        },
         "responses": {
           "default": {
             "description": "default response"
diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go
index 10e1d05..6650ad5 100644
--- a/openapi3/schema_test.go
+++ b/openapi3/schema_test.go
@@ -4,7 +4,9 @@ import (
 	"context"
 	"encoding/base64"
 	"encoding/json"
+	"fmt"
 	"math"
+	"reflect"
 	"strings"
 	"testing"
 
@@ -59,13 +61,13 @@ func testSchema(t *testing.T, example schemaExample) func(*testing.T) {
 	}
 }
 
-func validateSchema(t *testing.T, schema *Schema, value interface{}) error {
+func validateSchema(t *testing.T, schema *Schema, value interface{}, opts ...SchemaValidationOption) error {
 	data, err := json.Marshal(value)
 	require.NoError(t, err)
 	var val interface{}
 	err = json.Unmarshal(data, &val)
 	require.NoError(t, err)
-	return schema.VisitJSON(val)
+	return schema.VisitJSON(val, opts...)
 }
 
 var schemaExamples = []schemaExample{
@@ -1042,3 +1044,151 @@ var schemaErrorExamples = []schemaErrorExample{
 		Want: "NEST",
 	},
 }
+
+type schemaMultiErrorExample struct {
+	Title          string
+	Schema         *Schema
+	Values         []interface{}
+	ExpectedErrors []MultiError
+}
+
+func TestSchemasMultiError(t *testing.T) {
+	for _, example := range schemaMultiErrorExamples {
+		t.Run(example.Title, testSchemaMultiError(t, example))
+	}
+}
+
+func testSchemaMultiError(t *testing.T, example schemaMultiErrorExample) func(*testing.T) {
+	return func(t *testing.T) {
+		schema := example.Schema
+		for i, value := range example.Values {
+			err := validateSchema(t, schema, value, MultiErrors())
+			require.Error(t, err)
+			require.IsType(t, MultiError{}, err)
+
+			merr, _ := err.(MultiError)
+			expected := example.ExpectedErrors[i]
+			require.True(t, len(merr) > 0)
+			require.Len(t, merr, len(expected))
+			for _, e := range merr {
+				require.IsType(t, &SchemaError{}, e)
+				var found bool
+				scherr, _ := e.(*SchemaError)
+				for _, expectedErr := range expected {
+					expectedScherr, _ := expectedErr.(*SchemaError)
+					if reflect.DeepEqual(expectedScherr.reversePath, scherr.reversePath) &&
+						expectedScherr.SchemaField == scherr.SchemaField {
+						found = true
+						break
+					}
+				}
+				require.True(t, found, fmt.Sprintf("Missing %s error on %s", scherr.SchemaField, strings.Join(scherr.JSONPointer(), ".")))
+			}
+		}
+	}
+}
+
+var schemaMultiErrorExamples = []schemaMultiErrorExample{
+	{
+		Title: "STRING",
+		Schema: NewStringSchema().
+			WithMinLength(2).
+			WithMaxLength(3).
+			WithPattern("^[abc]+$"),
+		Values: []interface{}{
+			"f",
+			"foobar",
+		},
+		ExpectedErrors: []MultiError{
+			{&SchemaError{SchemaField: "minLength"}, &SchemaError{SchemaField: "pattern"}},
+			{&SchemaError{SchemaField: "maxLength"}, &SchemaError{SchemaField: "pattern"}},
+		},
+	},
+	{
+		Title: "NUMBER",
+		Schema: NewIntegerSchema().
+			WithMin(1).
+			WithMax(10),
+		Values: []interface{}{
+			0.5,
+			10.1,
+		},
+		ExpectedErrors: []MultiError{
+			{&SchemaError{SchemaField: "type"}, &SchemaError{SchemaField: "minimum"}},
+			{&SchemaError{SchemaField: "type"}, &SchemaError{SchemaField: "maximum"}},
+		},
+	},
+	{
+		Title: "ARRAY: simple",
+		Schema: NewArraySchema().
+			WithMinItems(2).
+			WithMaxItems(2).
+			WithItems(NewStringSchema().
+				WithPattern("^[abc]+$")),
+		Values: []interface{}{
+			[]interface{}{"foo"},
+			[]interface{}{"foo", "bar", "fizz"},
+		},
+		ExpectedErrors: []MultiError{
+			{
+				&SchemaError{SchemaField: "minItems"},
+				&SchemaError{SchemaField: "pattern", reversePath: []string{"0"}},
+			},
+			{
+				&SchemaError{SchemaField: "maxItems"},
+				&SchemaError{SchemaField: "pattern", reversePath: []string{"0"}},
+				&SchemaError{SchemaField: "pattern", reversePath: []string{"1"}},
+				&SchemaError{SchemaField: "pattern", reversePath: []string{"2"}},
+			},
+		},
+	},
+	{
+		Title: "ARRAY: object",
+		Schema: NewArraySchema().
+			WithItems(NewObjectSchema().
+				WithProperties(map[string]*Schema{
+					"key1": NewStringSchema(),
+					"key2": NewIntegerSchema(),
+				}),
+			),
+		Values: []interface{}{
+			[]interface{}{
+				map[string]interface{}{
+					"key1": 100, // not a string
+					"key2": "not an integer",
+				},
+			},
+		},
+		ExpectedErrors: []MultiError{
+			{
+				&SchemaError{SchemaField: "type", reversePath: []string{"key1", "0"}},
+				&SchemaError{SchemaField: "type", reversePath: []string{"key2", "0"}},
+			},
+		},
+	},
+	{
+		Title: "OBJECT",
+		Schema: NewObjectSchema().
+			WithProperties(map[string]*Schema{
+				"key1": NewStringSchema(),
+				"key2": NewIntegerSchema(),
+				"key3": NewArraySchema().
+					WithItems(NewStringSchema().
+						WithPattern("^[abc]+$")),
+			}),
+		Values: []interface{}{
+			map[string]interface{}{
+				"key1": 100, // not a string
+				"key2": "not an integer",
+				"key3": []interface{}{"abc", "def"},
+			},
+		},
+		ExpectedErrors: []MultiError{
+			{
+				&SchemaError{SchemaField: "type", reversePath: []string{"key1"}},
+				&SchemaError{SchemaField: "type", reversePath: []string{"key2"}},
+				&SchemaError{SchemaField: "pattern", reversePath: []string{"1", "key3"}},
+			},
+		},
+	},
+}
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index 737ceeb..0a28ab6 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -74,6 +74,9 @@ func getValidationTests(t *testing.T) []*validationTest {
 	unsupportedContentType := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
 	unsupportedContentType.Header.Set("Content-Type", "text/plain")
 
+	unsupportedHeaderValue := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
+	unsupportedHeaderValue.Header.Set("x-environment", "watdis")
+
 	return []*validationTest{
 		//
 		// Basics
@@ -270,10 +273,43 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 		},
 
+		//
+		// Request header params
+		//
+		{
+			name: "error - invalid enum value for header string parameter",
+			args: validationArgs{
+				r: unsupportedHeaderValue,
+			},
+			wantErrParam:        "x-environment",
+			wantErrParamIn:      "header",
+			wantErrSchemaReason: "JSON value is not one of the allowed values",
+			wantErrSchemaPath:   "/",
+			wantErrSchemaValue:  "watdis",
+			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
+				Title:  "JSON value is not one of the allowed values",
+				Detail: "Value 'watdis' at / must be one of: demo, prod",
+				Source: &ValidationErrorSource{Parameter: "x-environment"}},
+		},
+
 		//
 		// Request bodies
 		//
 
+		{
+			name: "error - invalid enum value for header object attribute",
+			args: validationArgs{
+				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
+			},
+			wantErrReason:       "doesn't match the schema",
+			wantErrSchemaReason: "JSON value is not one of the allowed values",
+			wantErrSchemaValue:  "watdis",
+			wantErrSchemaPath:   "/status",
+			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
+				Title:  "JSON value is not one of the allowed values",
+				Detail: "Value 'watdis' at /status must be one of: available, pending, sold",
+				Source: &ValidationErrorSource{Pointer: "/status"}},
+		},
 		{
 			name: "error - missing required object attribute",
 			args: validationArgs{