Repository: getkin/kin-openapi
Pull request #655 (2022-11-06 09:48:07)

Internalize recursive external references #618
Addressing issue #618 by tracking if parents are externally referenced through the recursion tree. 
Tests are passing along with repro posted in #635


Patch:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..a09546a
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+  schedule:
+    - cron: "4 8 * * 4"
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ go ]
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@v2
+        with:
+          languages: ${{ matrix.language }}
+          queries: +security-and-quality
+
+      - name: Autobuild
+        uses: github/codeql-action/autobuild@v2
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@v2
+        with:
+          category: "/language:${{ matrix.language }}"
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index 53b4b40..8c08205 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -485,6 +485,16 @@ func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
 	for i, v := range schema.Value.AllOf {
 		schema.Value.AllOf[i] = ToV3SchemaRef(v)
 	}
+	if val, ok := schema.Value.Extensions["x-nullable"]; ok {
+		var nullable bool
+
+		if err := json.Unmarshal(val.(json.RawMessage), &nullable); err == nil {
+			schema.Value.Nullable = nullable
+		}
+
+		delete(schema.Value.Extensions, "x-nullable")
+	}
+
 	return schema
 }
 
@@ -824,6 +834,11 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 	for i, v := range schema.Value.AllOf {
 		schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
 	}
+	if schema.Value.Nullable {
+		schema.Value.Nullable = false
+		schema.Value.Extensions["x-nullable"] = true
+	}
+
 	return schema, nil
 }
 
diff --git a/openapi3/errors.go b/openapi3/errors.go
index b530101..74baab9 100644
--- a/openapi3/errors.go
+++ b/openapi3/errors.go
@@ -10,11 +10,15 @@ import (
 type MultiError []error
 
 func (me MultiError) Error() string {
+	return spliceErr(" | ", me)
+}
+
+func spliceErr(sep string, errs []error) string {
 	buff := &bytes.Buffer{}
-	for i, e := range me {
+	for i, e := range errs {
 		buff.WriteString(e.Error())
-		if i != len(me)-1 {
-			buff.WriteString(" | ")
+		if i != len(errs)-1 {
+			buff.WriteString(sep)
 		}
 	}
 	return buff.String()
@@ -43,3 +47,13 @@ func (me MultiError) As(target interface{}) bool {
 	}
 	return false
 }
+
+type multiErrorForOneOf MultiError
+
+func (meo multiErrorForOneOf) Error() string {
+	return spliceErr(" Or ", meo)
+}
+
+func (meo multiErrorForOneOf) Unwrap() error {
+	return MultiError(meo)
+}
diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go
index 1733a49..6ff6b85 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -45,19 +45,19 @@ func parametersMapNames(s ParametersMap) []string {
 	return out
 }
 
-func isExternalRef(ref string) bool {
-	return ref != "" && !strings.HasPrefix(ref, "#/components/")
+func isExternalRef(ref string, parentIsExternal bool) bool {
+	return ref != "" && (!strings.HasPrefix(ref, "#/components/") || parentIsExternal)
 }
 
-func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver) {
-	if s == nil || !isExternalRef(s.Ref) {
-		return
+func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
+	if s == nil || !isExternalRef(s.Ref, parentIsExternal) {
+		return false
 	}
 
 	name := refNameResolver(s.Ref)
 	if _, ok := doc.Components.Schemas[name]; ok {
 		s.Ref = "#/components/schemas/" + name
-		return
+		return true
 	}
 
 	if doc.Components.Schemas == nil {
@@ -65,16 +65,17 @@ func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver) {
 	}
 	doc.Components.Schemas[name] = s.Value.NewRef()
 	s.Ref = "#/components/schemas/" + name
+	return true
 }
 
-func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver) {
-	if p == nil || !isExternalRef(p.Ref) {
-		return
+func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
+	if p == nil || !isExternalRef(p.Ref, parentIsExternal) {
+		return false
 	}
 	name := refNameResolver(p.Ref)
 	if _, ok := doc.Components.Parameters[name]; ok {
 		p.Ref = "#/components/parameters/" + name
-		return
+		return true
 	}
 
 	if doc.Components.Parameters == nil {
@@ -82,59 +83,62 @@ func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolve
 	}
 	doc.Components.Parameters[name] = &ParameterRef{Value: p.Value}
 	p.Ref = "#/components/parameters/" + name
+	return true
 }
 
-func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver) {
-	if h == nil || !isExternalRef(h.Ref) {
-		return
+func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
+	if h == nil || !isExternalRef(h.Ref, parentIsExternal) {
+		return false
 	}
 	name := refNameResolver(h.Ref)
 	if _, ok := doc.Components.Headers[name]; ok {
 		h.Ref = "#/components/headers/" + name
-		return
+		return true
 	}
 	if doc.Components.Headers == nil {
 		doc.Components.Headers = make(Headers)
 	}
 	doc.Components.Headers[name] = &HeaderRef{Value: h.Value}
 	h.Ref = "#/components/headers/" + name
+	return true
 }
 
-func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver) {
-	if r == nil || !isExternalRef(r.Ref) {
-		return
+func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
+	if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
+		return false
 	}
 	name := refNameResolver(r.Ref)
 	if _, ok := doc.Components.RequestBodies[name]; ok {
 		r.Ref = "#/components/requestBodies/" + name
-		return
+		return true
 	}
 	if doc.Components.RequestBodies == nil {
 		doc.Components.RequestBodies = make(RequestBodies)
 	}
 	doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value}
 	r.Ref = "#/components/requestBodies/" + name
+	return true
 }
 
-func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver) {
-	if r == nil || !isExternalRef(r.Ref) {
-		return
+func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
+	if r == nil || !isExternalRef(r.Ref, parentIsExternal) {
+		return false
 	}
 	name := refNameResolver(r.Ref)
 	if _, ok := doc.Components.Responses[name]; ok {
 		r.Ref = "#/components/responses/" + name
-		return
+		return true
 	}
 	if doc.Components.Responses == nil {
 		doc.Components.Responses = make(Responses)
 	}
 	doc.Components.Responses[name] = &ResponseRef{Value: r.Value}
 	r.Ref = "#/components/responses/" + name
-
+	return true
 }
 
-func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver) {
-	if ss == nil || !isExternalRef(ss.Ref) {
+func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver, parentIsExternal bool) {
+	if ss == nil || !isExternalRef(ss.Ref, parentIsExternal) {
 		return
 	}
 	name := refNameResolver(ss.Ref)
@@ -150,8 +154,8 @@ func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver Ref
 
 }
 
-func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver) {
-	if e == nil || !isExternalRef(e.Ref) {
+func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver, parentIsExternal bool) {
+	if e == nil || !isExternalRef(e.Ref, parentIsExternal) {
 		return
 	}
 	name := refNameResolver(e.Ref)
@@ -167,8 +171,8 @@ func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver) {
 
 }
 
-func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver) {
-	if l == nil || !isExternalRef(l.Ref) {
+func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver, parentIsExternal bool) {
+	if l == nil || !isExternalRef(l.Ref, parentIsExternal) {
 		return
 	}
 	name := refNameResolver(l.Ref)
@@ -184,9 +188,9 @@ func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver) {
 
 }
 
-func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver) {
-	if c == nil || !isExternalRef(c.Ref) {
-		return
+func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver, parentIsExternal bool) bool {
+	if c == nil || !isExternalRef(c.Ref, parentIsExternal) {
+		return false
 	}
 	name := refNameResolver(c.Ref)
 	if _, ok := doc.Components.Callbacks[name]; ok {
@@ -197,118 +201,119 @@ func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver)
 	}
 	doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
 	c.Ref = "#/components/callbacks/" + name
+	return true
 }
 
-func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver) {
+func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsExternal bool) {
 	if s == nil || doc.isVisitedSchema(s) {
 		return
 	}
 
 	for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} {
 		for _, s2 := range list {
-			doc.addSchemaToSpec(s2, refNameResolver)
+			isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
 			if s2 != nil {
-				doc.derefSchema(s2.Value, refNameResolver)
+				doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
 			}
 		}
 	}
 	for _, s2 := range s.Properties {
-		doc.addSchemaToSpec(s2, refNameResolver)
+		isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal)
 		if s2 != nil {
-			doc.derefSchema(s2.Value, refNameResolver)
+			doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
 		}
 	}
 	for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties, s.Items} {
-		doc.addSchemaToSpec(ref, refNameResolver)
+		isExternal := doc.addSchemaToSpec(ref, refNameResolver, parentIsExternal)
 		if ref != nil {
-			doc.derefSchema(ref.Value, refNameResolver)
+			doc.derefSchema(ref.Value, refNameResolver, isExternal || parentIsExternal)
 		}
 	}
 }
 
-func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver) {
+func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIsExternal bool) {
 	for _, h := range hs {
-		doc.addHeaderToSpec(h, refNameResolver)
+		isExternal := doc.addHeaderToSpec(h, refNameResolver, parentIsExternal)
 		if doc.isVisitedHeader(h.Value) {
 			continue
 		}
-		doc.derefParameter(h.Value.Parameter, refNameResolver)
+		doc.derefParameter(h.Value.Parameter, refNameResolver, parentIsExternal || isExternal)
 	}
 }
 
-func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver) {
+func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver, parentIsExternal bool) {
 	for _, e := range es {
-		doc.addExampleToSpec(e, refNameResolver)
+		doc.addExampleToSpec(e, refNameResolver, parentIsExternal)
 	}
 }
 
-func (doc *T) derefContent(c Content, refNameResolver RefNameResolver) {
+func (doc *T) derefContent(c Content, refNameResolver RefNameResolver, parentIsExternal bool) {
 	for _, mediatype := range c {
-		doc.addSchemaToSpec(mediatype.Schema, refNameResolver)
+		isExternal := doc.addSchemaToSpec(mediatype.Schema, refNameResolver, parentIsExternal)
 		if mediatype.Schema != nil {
-			doc.derefSchema(mediatype.Schema.Value, refNameResolver)
+			doc.derefSchema(mediatype.Schema.Value, refNameResolver, isExternal || parentIsExternal)
 		}
-		doc.derefExamples(mediatype.Examples, refNameResolver)
+		doc.derefExamples(mediatype.Examples, refNameResolver, parentIsExternal)
 		for _, e := range mediatype.Encoding {
-			doc.derefHeaders(e.Headers, refNameResolver)
+			doc.derefHeaders(e.Headers, refNameResolver, parentIsExternal)
 		}
 	}
 }
 
-func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver) {
+func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver, parentIsExternal bool) {
 	for _, l := range ls {
-		doc.addLinkToSpec(l, refNameResolver)
+		doc.addLinkToSpec(l, refNameResolver, parentIsExternal)
 	}
 }
 
-func (doc *T) derefResponses(es Responses, refNameResolver RefNameResolver) {
+func (doc *T) derefResponses(es Responses, refNameResolver RefNameResolver, parentIsExternal bool) {
 	for _, e := range es {
-		doc.addResponseToSpec(e, refNameResolver)
+		isExternal := doc.addResponseToSpec(e, refNameResolver, parentIsExternal)
 		if e.Value != nil {
-			doc.derefHeaders(e.Value.Headers, refNameResolver)
-			doc.derefContent(e.Value.Content, refNameResolver)
-			doc.derefLinks(e.Value.Links, refNameResolver)
+			doc.derefHeaders(e.Value.Headers, refNameResolver, isExternal || parentIsExternal)
+			doc.derefContent(e.Value.Content, refNameResolver, isExternal || parentIsExternal)
+			doc.derefLinks(e.Value.Links, refNameResolver, isExternal || parentIsExternal)
 		}
 	}
 }
 
-func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver) {
-	doc.addSchemaToSpec(p.Schema, refNameResolver)
-	doc.derefContent(p.Content, refNameResolver)
+func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver, parentIsExternal bool) {
+	isExternal := doc.addSchemaToSpec(p.Schema, refNameResolver, parentIsExternal)
+	doc.derefContent(p.Content, refNameResolver, parentIsExternal)
 	if p.Schema != nil {
-		doc.derefSchema(p.Schema.Value, refNameResolver)
+		doc.derefSchema(p.Schema.Value, refNameResolver, isExternal || parentIsExternal)
 	}
 }
 
-func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver) {
-	doc.derefContent(r.Content, refNameResolver)
+func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, parentIsExternal bool) {
+	doc.derefContent(r.Content, refNameResolver, parentIsExternal)
 }
 
-func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver) {
+func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) {
 	for _, ops := range paths {
 		// inline full operations
 		ops.Ref = ""
 
 		for _, param := range ops.Parameters {
-			doc.addParameterToSpec(param, refNameResolver)
+			doc.addParameterToSpec(param, refNameResolver, parentIsExternal)
 		}
 
 		for _, op := range ops.Operations() {
-			doc.addRequestBodyToSpec(op.RequestBody, refNameResolver)
+			isExternal := doc.addRequestBodyToSpec(op.RequestBody, refNameResolver, parentIsExternal)
 			if op.RequestBody != nil && op.RequestBody.Value != nil {
-				doc.derefRequestBody(*op.RequestBody.Value, refNameResolver)
+				doc.derefRequestBody(*op.RequestBody.Value, refNameResolver, parentIsExternal || isExternal)
 			}
 			for _, cb := range op.Callbacks {
-				doc.addCallbackToSpec(cb, refNameResolver)
+				isExternal := doc.addCallbackToSpec(cb, refNameResolver, parentIsExternal)
 				if cb.Value != nil {
-					doc.derefPaths(*cb.Value, refNameResolver)
+					doc.derefPaths(*cb.Value, refNameResolver, parentIsExternal || isExternal)
 				}
 			}
-			doc.derefResponses(op.Responses, refNameResolver)
+			doc.derefResponses(op.Responses, refNameResolver, parentIsExternal)
 			for _, param := range op.Parameters {
-				doc.addParameterToSpec(param, refNameResolver)
+				isExternal := doc.addParameterToSpec(param, refNameResolver, parentIsExternal)
 				if param.Value != nil {
-					doc.derefParameter(*param.Value, refNameResolver)
+					doc.derefParameter(*param.Value, refNameResolver, parentIsExternal || isExternal)
 				}
 			}
 		}
@@ -337,42 +342,42 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri
 	names := schemaNames(doc.Components.Schemas)
 	for _, name := range names {
 		schema := doc.Components.Schemas[name]
-		doc.addSchemaToSpec(schema, refNameResolver)
+		isExternal := doc.addSchemaToSpec(schema, refNameResolver, false)
 		if schema != nil {
 			schema.Ref = "" // always dereference the top level
-			doc.derefSchema(schema.Value, refNameResolver)
+			doc.derefSchema(schema.Value, refNameResolver, isExternal)
 		}
 	}
 	names = parametersMapNames(doc.Components.Parameters)
 	for _, name := range names {
 		p := doc.Components.Parameters[name]
-		doc.addParameterToSpec(p, refNameResolver)
+		isExternal := doc.addParameterToSpec(p, refNameResolver, false)
 		if p != nil && p.Value != nil {
 			p.Ref = "" // always dereference the top level
-			doc.derefParameter(*p.Value, refNameResolver)
+			doc.derefParameter(*p.Value, refNameResolver, isExternal)
 		}
 	}
-	doc.derefHeaders(doc.Components.Headers, refNameResolver)
+	doc.derefHeaders(doc.Components.Headers, refNameResolver, false)
 	for _, req := range doc.Components.RequestBodies {
-		doc.addRequestBodyToSpec(req, refNameResolver)
+		isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false)
 		if req != nil && req.Value != nil {
 			req.Ref = "" // always dereference the top level
-			doc.derefRequestBody(*req.Value, refNameResolver)
+			doc.derefRequestBody(*req.Value, refNameResolver, isExternal)
 		}
 	}
-	doc.derefResponses(doc.Components.Responses, refNameResolver)
+	doc.derefResponses(doc.Components.Responses, refNameResolver, false)
 	for _, ss := range doc.Components.SecuritySchemes {
-		doc.addSecuritySchemeToSpec(ss, refNameResolver)
+		doc.addSecuritySchemeToSpec(ss, refNameResolver, false)
 	}
-	doc.derefExamples(doc.Components.Examples, refNameResolver)
-	doc.derefLinks(doc.Components.Links, refNameResolver)
+	doc.derefExamples(doc.Components.Examples, refNameResolver, false)
+	doc.derefLinks(doc.Components.Links, refNameResolver, false)
 	for _, cb := range doc.Components.Callbacks {
-		doc.addCallbackToSpec(cb, refNameResolver)
+		isExternal := doc.addCallbackToSpec(cb, refNameResolver, false)
 		if cb != nil && cb.Value != nil {
 			cb.Ref = "" // always dereference the top level
-			doc.derefPaths(*cb.Value, refNameResolver)
+			doc.derefPaths(*cb.Value, refNameResolver, isExternal)
 		}
 	}
 
-	doc.derefPaths(doc.Paths, refNameResolver)
+	doc.derefPaths(doc.Paths, refNameResolver, false)
 }
diff --git a/openapi3/loader.go b/openapi3/loader.go
index 1ab693b..138431f 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -742,9 +742,16 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 				return err
 			}
 			component.Value = resolved.Value
-			foundPath := loader.getResolvedRefPath(ref, &resolved, documentPath, componentPath)
+			foundPath, rerr := loader.getResolvedRefPath(ref, &resolved, documentPath, componentPath)
+			if rerr != nil {
+				return fmt.Errorf("failed to resolve file from reference %q: %w", ref, rerr)
+			}
 			documentPath = loader.documentPathForRecursiveRef(documentPath, foundPath)
 		}
+		if loader.visitedSchema == nil {
+			loader.visitedSchema = make(map[*Schema]struct{})
+		}
+		loader.visitedSchema[component.Value] = struct{}{}
 	}
 	value := component.Value
 	if value == nil {
@@ -790,23 +797,28 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 	return nil
 }
 
-func (loader *Loader) getResolvedRefPath(ref string, resolved *SchemaRef, cur, found *url.URL) string {
+func (loader *Loader) getResolvedRefPath(ref string, resolved *SchemaRef, cur, found *url.URL) (string, error) {
 	if referencedFilename := strings.Split(ref, "#")[0]; referencedFilename == "" {
 		if cur != nil {
 			if loader.rootDir != "" && strings.HasPrefix(cur.Path, loader.rootDir) {
-				return cur.Path[len(loader.rootDir)+1:]
+				return cur.Path[len(loader.rootDir)+1:], nil
 			}
 
-			return path.Base(cur.Path)
+			return path.Base(cur.Path), nil
 		}
-		return ""
+		return "", nil
 	}
 	// ref. to external file
 	if resolved.Ref != "" {
-		return resolved.Ref
+		return resolved.Ref, nil
 	}
+
+	if loader.rootDir == "" {
+		return found.Path, nil
+	}
+
 	// found dest spec. file
-	return path.Dir(found.Path)[len(loader.rootDir):]
+	return filepath.Rel(loader.rootDir, found.Path)
 }
 
 func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) {
diff --git a/openapi3/schema.go b/openapi3/schema.go
index ba846d0..d2cd31c 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -861,10 +861,11 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
 		}
 	}
 	return &SchemaError{
-		Value:       value,
-		Schema:      schema,
-		SchemaField: "type",
-		Reason:      fmt.Sprintf("unhandled value of type %T", value),
+		Value:                 value,
+		Schema:                schema,
+		SchemaField:           "type",
+		Reason:                fmt.Sprintf("unhandled value of type %T", value),
+		customizeMessageError: settings.customizeMessageError,
 	}
 }
 
@@ -879,10 +880,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			return errSchema
 		}
 		return &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "enum",
-			Reason:      "value is not one of the allowed values",
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "enum",
+			Reason:                fmt.Sprintf("value %q is not one of the allowed values", value),
+			customizeMessageError: settings.customizeMessageError,
 		}
 	}
 
@@ -896,9 +898,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				return errSchema
 			}
 			return &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "not",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "not",
+				customizeMessageError: settings.customizeMessageError,
 			}
 		}
 	}
@@ -926,7 +929,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 
 		var (
 			ok               = 0
-			validationErrors = []error{}
+			validationErrors = multiErrorForOneOf{}
 			matchedOneOfIdx  = 0
 			tempValue        = value
 		)
@@ -955,22 +958,16 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 
 		if ok != 1 {
 			if len(validationErrors) > 1 {
-				errorMessage := ""
-				for _, err := range validationErrors {
-					if errorMessage != "" {
-						errorMessage += " Or "
-					}
-					errorMessage += err.Error()
-				}
-				return errors.New("doesn't match schema due to: " + errorMessage)
+				return fmt.Errorf("doesn't match schema due to: %w", validationErrors)
 			}
 			if settings.failfast {
 				return errSchema
 			}
 			e := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "oneOf",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "oneOf",
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if ok > 1 {
 				e.Origin = ErrOneOfConflict
@@ -1012,9 +1009,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				return errSchema
 			}
 			return &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "anyOf",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "anyOf",
+				customizeMessageError: settings.customizeMessageError,
 			}
 		}
 
@@ -1031,10 +1029,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				return errSchema
 			}
 			return &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "allOf",
-				Origin:      err,
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "allOf",
+				Origin:                err,
+				customizeMessageError: settings.customizeMessageError,
 			}
 		}
 	}
@@ -1049,10 +1048,11 @@ func (schema *Schema) visitJSONNull(settings *schemaValidationSettings) (err err
 		return errSchema
 	}
 	return &SchemaError{
-		Value:       nil,
-		Schema:      schema,
-		SchemaField: "nullable",
-		Reason:      "Value is not nullable",
+		Value:                 nil,
+		Schema:                schema,
+		SchemaField:           "nullable",
+		Reason:                "Value is not nullable",
+		customizeMessageError: settings.customizeMessageError,
 	}
 }
 
@@ -1082,10 +1082,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "type",
-				Reason:      "Value must be an integer",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "type",
+				Reason:                fmt.Sprintf("value \"%g\" must be an integer", value),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1117,10 +1118,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "format",
-				Reason:      fmt.Sprintf("number must be an %s", schema.Format),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "format",
+				Reason:                fmt.Sprintf("number must be an %s", schema.Format),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1135,10 +1137,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "exclusiveMinimum",
-			Reason:      fmt.Sprintf("number must be more than %g", *schema.Min),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "exclusiveMinimum",
+			Reason:                fmt.Sprintf("number must be more than %g", *schema.Min),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1152,10 +1155,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "exclusiveMaximum",
-			Reason:      fmt.Sprintf("number must be less than %g", *schema.Max),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "exclusiveMaximum",
+			Reason:                fmt.Sprintf("number must be less than %g", *schema.Max),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1169,10 +1173,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "minimum",
-			Reason:      fmt.Sprintf("number must be at least %g", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "minimum",
+			Reason:                fmt.Sprintf("number must be at least %g", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1186,10 +1191,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "maximum",
-			Reason:      fmt.Sprintf("number must be at most %g", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "maximum",
+			Reason:                fmt.Sprintf("number must be at most %g", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1206,9 +1212,10 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "multipleOf",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "multipleOf",
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1254,10 +1261,11 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "minLength",
-				Reason:      fmt.Sprintf("minimum string length is %d", minLength),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "minLength",
+				Reason:                fmt.Sprintf("minimum string length is %d", minLength),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1269,10 +1277,11 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "maxLength",
-				Reason:      fmt.Sprintf("maximum string length is %d", *maxLength),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "maxLength",
+				Reason:                fmt.Sprintf("maximum string length is %d", *maxLength),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1293,10 +1302,11 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 	}
 	if cp := schema.compiledPattern; cp != nil && !cp.MatchString(value) {
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "pattern",
-			Reason:      fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "pattern",
+			Reason:                fmt.Sprintf(`string %q doesn't match the regular expression "%s"`, value, schema.Pattern),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1305,29 +1315,32 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 	}
 
 	// "format"
-	var formatErr string
+	var formatStrErr string
+	var formatErr error
 	if format := schema.Format; format != "" {
 		if f, ok := SchemaStringFormats[format]; ok {
 			switch {
 			case f.regexp != nil && f.callback == nil:
 				if cp := f.regexp; !cp.MatchString(value) {
-					formatErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String())
+					formatStrErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String())
 				}
 			case f.regexp == nil && f.callback != nil:
 				if err := f.callback(value); err != nil {
-					formatErr = err.Error()
+					formatErr = err
 				}
 			default:
-				formatErr = fmt.Sprintf("corrupted entry %q in SchemaStringFormats", format)
+				formatStrErr = fmt.Sprintf("corrupted entry %q in SchemaStringFormats", format)
 			}
 		}
 	}
-	if formatErr != "" {
+	if formatStrErr != "" || formatErr != nil {
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "format",
-			Reason:      formatErr,
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "format",
+			Reason:                formatStrErr,
+			Origin:                formatErr,
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1363,10 +1376,11 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "minItems",
-			Reason:      fmt.Sprintf("minimum number of items is %d", v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "minItems",
+			Reason:                fmt.Sprintf("minimum number of items is %d", v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1380,10 +1394,11 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "maxItems",
-			Reason:      fmt.Sprintf("maximum number of items is %d", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "maxItems",
+			Reason:                fmt.Sprintf("maximum number of items is %d", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1400,10 +1415,11 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "uniqueItems",
-			Reason:      "duplicate items found",
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "uniqueItems",
+			Reason:                "duplicate items found",
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1491,10 +1507,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "minProperties",
-			Reason:      fmt.Sprintf("there must be at least %d properties", v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "minProperties",
+			Reason:                fmt.Sprintf("there must be at least %d properties", v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1508,10 +1525,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "maxProperties",
-			Reason:      fmt.Sprintf("there must be at most %d properties", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "maxProperties",
+			Reason:                fmt.Sprintf("there must be at most %d properties", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1579,10 +1597,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "properties",
-			Reason:      fmt.Sprintf("property %q is unsupported", k),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "properties",
+			Reason:                fmt.Sprintf("property %q is unsupported", k),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1603,10 +1622,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := markSchemaErrorKey(&SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "required",
-				Reason:      fmt.Sprintf("property %q is missing", k),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "required",
+				Reason:                fmt.Sprintf("property %q is missing", k),
+				customizeMessageError: settings.customizeMessageError,
 			}, k)
 			if !settings.multiError {
 				return err
@@ -1627,10 +1647,11 @@ func (schema *Schema) expectedType(settings *schemaValidationSettings, typ strin
 		return errSchema
 	}
 	return &SchemaError{
-		Value:       typ,
-		Schema:      schema,
-		SchemaField: "type",
-		Reason:      "Field must be set to " + schema.Type + " or not be present",
+		Value:                 typ,
+		Schema:                schema,
+		SchemaField:           "type",
+		Reason:                fmt.Sprintf("field must be set to %s or not be present", schema.Type),
+		customizeMessageError: settings.customizeMessageError,
 	}
 }
 
@@ -1646,17 +1667,24 @@ func (schema *Schema) compilePattern() (err error) {
 }
 
 type SchemaError struct {
-	Value       interface{}
-	reversePath []string
-	Schema      *Schema
-	SchemaField string
-	Reason      string
-	Origin      error
+	Value                 interface{}
+	reversePath           []string
+	Schema                *Schema
+	SchemaField           string
+	Reason                string
+	Origin                error
+	customizeMessageError func(err *SchemaError) string
 }
 
 var _ interface{ Unwrap() error } = SchemaError{}
 
 func markSchemaErrorKey(err error, key string) error {
+	var me multiErrorForOneOf
+
+	if errors.As(err, &me) {
+		err = me.Unwrap()
+	}
+
 	if v, ok := err.(*SchemaError); ok {
 		v.reversePath = append(v.reversePath, key)
 		return v
@@ -1671,17 +1699,7 @@ func markSchemaErrorKey(err error, key string) error {
 }
 
 func markSchemaErrorIndex(err error, index int) error {
-	if v, ok := err.(*SchemaError); ok {
-		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
+	return markSchemaErrorKey(err, strconv.FormatInt(int64(index), 10))
 }
 
 func (err *SchemaError) JSONPointer() []string {
@@ -1694,11 +1712,14 @@ func (err *SchemaError) JSONPointer() []string {
 }
 
 func (err *SchemaError) Error() string {
-	if err.Origin != nil {
-		return err.Origin.Error()
+	if err.customizeMessageError != nil {
+		if msg := err.customizeMessageError(err); msg != "" {
+			return msg
+		}
 	}
 
 	buf := bytes.NewBuffer(make([]byte, 0, 256))
+
 	if len(err.reversePath) > 0 {
 		buf.WriteString(`Error at "`)
 		reversePath := err.reversePath
@@ -1708,6 +1729,13 @@ func (err *SchemaError) Error() string {
 		}
 		buf.WriteString(`": `)
 	}
+
+	if err.Origin != nil {
+		buf.WriteString(err.Origin.Error())
+
+		return buf.String()
+	}
+
 	reason := err.Reason
 	if reason == "" {
 		buf.WriteString(`Doesn't match schema "`)
@@ -1716,6 +1744,7 @@ func (err *SchemaError) Error() string {
 	} else {
 		buf.WriteString(reason)
 	}
+
 	if !SchemaErrorDetailsDisabled {
 		buf.WriteString("\nSchema:\n  ")
 		encoder := json.NewEncoder(buf)
@@ -1728,6 +1757,7 @@ func (err *SchemaError) Error() string {
 			panic(err)
 		}
 	}
+
 	return buf.String()
 }
 
diff --git a/openapi3/schema_validation_settings.go b/openapi3/schema_validation_settings.go
index 854ae84..5a28c8d 100644
--- a/openapi3/schema_validation_settings.go
+++ b/openapi3/schema_validation_settings.go
@@ -16,6 +16,8 @@ type schemaValidationSettings struct {
 
 	onceSettingDefaults sync.Once
 	defaultsSet         func()
+
+	customizeMessageError func(err *SchemaError) string
 }
 
 // FailFast returns schema validation errors quicker.
@@ -50,6 +52,12 @@ func DefaultsSet(f func()) SchemaValidationOption {
 	return func(s *schemaValidationSettings) { s.defaultsSet = f }
 }
 
+// SetSchemaErrorMessageCustomizer allows to override the schema error message.
+// If the passed function returns an empty string, it returns to the previous Error() implementation.
+func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption {
+	return func(s *schemaValidationSettings) { s.customizeMessageError = f }
+}
+
 func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
 	settings := &schemaValidationSettings{}
 	for _, opt := range opts {
diff --git a/openapi3filter/options.go b/openapi3filter/options.go
index 1622339..14c35d5 100644
--- a/openapi3filter/options.go
+++ b/openapi3filter/options.go
@@ -1,5 +1,7 @@
 package openapi3filter
 
+import "github.com/getkin/kin-openapi/openapi3"
+
 // DefaultOptions do not set an AuthenticationFunc.
 // A spec with security schemes defined will not pass validation
 // unless an AuthenticationFunc is defined.
@@ -21,4 +23,19 @@ type Options struct {
 
 	// See NoopAuthenticationFunc
 	AuthenticationFunc AuthenticationFunc
+
+	// Indicates whether default values are set in the
+	// request. If true, then they are not set
+	SkipSettingDefaults bool
+
+	customSchemaErrorFunc CustomSchemaErrorFunc
+}
+
+// CustomSchemaErrorFunc allows for custom the schema error message.
+type CustomSchemaErrorFunc func(err *openapi3.SchemaError) string
+
+// WithCustomSchemaErrorFunc sets a function to override the schema error message.
+// If the passed function returns an empty string, it returns to the previous Error() implementation.
+func (o *Options) WithCustomSchemaErrorFunc(f CustomSchemaErrorFunc) {
+	o.customSchemaErrorFunc = f
 }
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index ca81944..029a84c 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -334,6 +334,9 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 	case *pathParamDecoder:
 		_, found = vDecoder.pathParams[param]
 	case *urlValuesDecoder:
+		if schema.Value.Pattern != "" {
+			return dec.DecodePrimitive(param, sm, schema)
+		}
 		_, found = vDecoder.values[param]
 	case *headerParamDecoder:
 		_, found = vDecoder.header[param]
@@ -500,6 +503,10 @@ func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.Serializat
 		// HTTP request does not contain a value of the target query parameter.
 		return nil, ok, nil
 	}
+
+	if schema.Value.Type == "" && schema.Value.Pattern != "" {
+		return values[0], ok, nil
+	}
 	val, err := parsePrimitive(values[0], schema)
 	return val, ok, err
 }
@@ -526,10 +533,91 @@ func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationM
 		}
 		values = strings.Split(values[0], delim)
 	}
-	val, err := parseArray(values, schema)
+	val, err := d.parseArray(values, sm, schema)
 	return val, ok, err
 }
 
+// parseArray returns an array that contains items from a raw array.
+// Every item is parsed as a primitive value.
+// The function returns an error when an error happened while parse array's items.
+func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMethod, schemaRef *openapi3.SchemaRef) ([]interface{}, error) {
+	var value []interface{}
+
+	for i, v := range raw {
+		item, err := d.parseValue(v, schemaRef.Value.Items)
+		if err != nil {
+			if v, ok := err.(*ParseError); ok {
+				return nil, &ParseError{path: []interface{}{i}, Cause: v}
+			}
+			return nil, fmt.Errorf("item %d: %w", i, err)
+		}
+
+		// If the items are nil, then the array is nil. There shouldn't be case where some values are actual primitive
+		// values and some are nil values.
+		if item == nil {
+			return nil, nil
+		}
+		value = append(value, item)
+	}
+	return value, nil
+}
+
+func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (interface{}, error) {
+	if len(schema.Value.AllOf) > 0 {
+		var value interface{}
+		var err error
+		for _, sr := range schema.Value.AllOf {
+			value, err = d.parseValue(v, sr)
+			if value == nil || err != nil {
+				break
+			}
+		}
+		return value, err
+	}
+
+	if len(schema.Value.AnyOf) > 0 {
+		var value interface{}
+		var err error
+		for _, sr := range schema.Value.AnyOf {
+			if value, err = d.parseValue(v, sr); err == nil {
+				return value, nil
+			}
+		}
+
+		return nil, err
+	}
+
+	if len(schema.Value.OneOf) > 0 {
+		isMatched := 0
+		var value interface{}
+		var err error
+		for _, sr := range schema.Value.OneOf {
+			result, err := d.parseValue(v, sr)
+			if err == nil {
+				value = result
+				isMatched++
+			}
+		}
+		if isMatched == 1 {
+			return value, nil
+		} else if isMatched > 1 {
+			return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
+		} else if isMatched == 0 {
+			return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
+		}
+
+		return nil, err
+	}
+
+	if schema.Value.Not != nil {
+		// TODO(decode not): handle decoding "not" JSON Schema
+		return nil, errors.New("not implemented: decoding 'not'")
+	}
+
+	return parsePrimitive(v, schema)
+
+}
+
 func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) {
 	var propsFn func(url.Values) (map[string]string, error)
 	switch sm.Style {
diff --git a/openapi3filter/req_resp_encoder.go b/openapi3filter/req_resp_encoder.go
index dd410f5..36b7db6 100644
--- a/openapi3filter/req_resp_encoder.go
+++ b/openapi3filter/req_resp_encoder.go
@@ -16,8 +16,34 @@ func encodeBody(body interface{}, mediaType string) ([]byte, error) {
 	return encoder(body)
 }
 
-type bodyEncoder func(body interface{}) ([]byte, error)
+type BodyEncoder func(body interface{}) ([]byte, error)
 
-var bodyEncoders = map[string]bodyEncoder{
+var bodyEncoders = map[string]BodyEncoder{
 	"application/json": json.Marshal,
 }
+
+func RegisterBodyEncoder(contentType string, encoder BodyEncoder) {
+	if contentType == "" {
+		panic("contentType is empty")
+	}
+	if encoder == nil {
+		panic("encoder is not defined")
+	}
+	bodyEncoders[contentType] = encoder
+}
+
+// This call is not thread-safe: body encoders should not be created/destroyed by multiple goroutines.
+func UnregisterBodyEncoder(contentType string) {
+	if contentType == "" {
+		panic("contentType is empty")
+	}
+	delete(bodyEncoders, contentType)
+}
+
+// RegisteredBodyEncoder returns the registered body encoder for the given content type.
+//
+// If no encoder was registered for the given content type, nil is returned.
+// This call is not thread-safe: body encoders should not be created/destroyed by multiple goroutines.
+func RegisteredBodyEncoder(contentType string) BodyEncoder {
+	return bodyEncoders[contentType]
+}
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 4b0bd34..4acb9ff 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -178,6 +178,9 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param
 		opts = make([]openapi3.SchemaValidationOption, 0, 1)
 		opts = append(opts, openapi3.MultiErrors())
 	}
+	if options.customSchemaErrorFunc != nil {
+		opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
+	}
 	if err = schema.VisitJSON(value, opts...); err != nil {
 		return &RequestError{Input: input, Parameter: parameter, Err: err}
 	}
@@ -258,10 +261,15 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 	defaultsSet := false
 	opts := make([]openapi3.SchemaValidationOption, 0, 3) // 3 potential opts here
 	opts = append(opts, openapi3.VisitAsRequest())
-	opts = append(opts, openapi3.DefaultsSet(func() { defaultsSet = true }))
+	if !options.SkipSettingDefaults {
+		opts = append(opts, openapi3.DefaultsSet(func() { defaultsSet = true }))
+	}
 	if options.MultiError {
 		opts = append(opts, openapi3.MultiErrors())
 	}
+	if options.customSchemaErrorFunc != nil {
+		opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
+	}
 
 	// Validate JSON with the schema
 	if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
@@ -285,6 +293,7 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 		}
 		// Put the data back into the input
 		req.Body = ioutil.NopCloser(bytes.NewReader(data))
+		req.ContentLength = int64(len(data))
 	}
 
 	return nil
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index e90b5d6..abcbb4e 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -66,6 +66,9 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 	if options.MultiError {
 		opts = append(opts, openapi3.MultiErrors())
 	}
+	if options.customSchemaErrorFunc != nil {
+		opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
+	}
 
 	headers := make([]string, 0, len(response.Headers))
 	for k := range response.Headers {


Test patch:
diff --git a/openapi2conv/openapi2_conv_test.go b/openapi2conv/openapi2_conv_test.go
index 9edce54..3177721 100644
--- a/openapi2conv/openapi2_conv_test.go
+++ b/openapi2conv/openapi2_conv_test.go
@@ -89,7 +89,8 @@ const exampleV2 = `
 			"additionalProperties": true,
 			"properties": {
 				"foo": {
-					"type": "string"
+					"type": "string",
+					"x-nullable": true
 				},
 				"quux": {
 					"$ref": "#/definitions/ItemExtension"
@@ -463,7 +464,8 @@ const exampleV3 = `
 				"additionalProperties": true,
 				"properties": {
 					"foo": {
-						"type": "string"
+						"type": "string",
+						"nullable": true
 					},
 					"quux": {
 						"$ref": "#/components/schemas/ItemExtension"
diff --git a/openapi3/issue136_test.go b/openapi3/issue136_test.go
index 36c8b85..b5e9eeb 100644
--- a/openapi3/issue136_test.go
+++ b/openapi3/issue136_test.go
@@ -31,7 +31,7 @@ components:
 		},
 		{
 			dflt: `1`,
-			err:  "invalid components: invalid schema default: Field must be set to string or not be present",
+			err:  "invalid components: invalid schema default: field must be set to string or not be present",
 		},
 	} {
 		t.Run(testcase.dflt, func(t *testing.T) {
diff --git a/openapi3/issue618_test.go b/openapi3/issue618_test.go
new file mode 100644
index 0000000..2085ca0
--- /dev/null
+++ b/openapi3/issue618_test.go
@@ -0,0 +1,39 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue618(t *testing.T) {
+	spec := `
+openapi: 3.0.0
+info:
+  title: foo
+  version: 0.0.0
+paths:
+  /foo:
+    get:
+      responses:
+        '200':
+          description: Some description value text
+          content:
+            application/json:
+              schema:
+                $ref: ./testdata/schema618.yml#/components/schemas/JournalEntry
+`[1:]
+
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	ctx := loader.Context
+
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	doc.InternalizeRefs(ctx, nil)
+
+	require.Contains(t, doc.Components.Schemas, "JournalEntry")
+	require.Contains(t, doc.Components.Schemas, "Record")
+	require.Contains(t, doc.Components.Schemas, "Account")
+}
diff --git a/openapi3/issue652_test.go b/openapi3/issue652_test.go
new file mode 100644
index 0000000..f36e920
--- /dev/null
+++ b/openapi3/issue652_test.go
@@ -0,0 +1,29 @@
+package openapi3_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func TestIssue652(t *testing.T) {
+	loader := openapi3.NewLoader()
+	loader.IsExternalRefsAllowed = true
+
+	// Test checks that no slice bounds out of range error occurs while loading
+	// from file that contains reference to file in the parent directory.
+	require.NotPanics(t, func() {
+		const schemaName = "ReferenceToParentDirectory"
+
+		spec, err := loader.LoadFromFile("testdata/issue652/nested/schema.yml")
+		require.NoError(t, err)
+		require.Contains(t, spec.Components.Schemas, schemaName)
+
+		schema := spec.Components.Schemas[schemaName]
+		assert.Equal(t, schema.Ref, "../definitions.yml#/components/schemas/TestSchema")
+		assert.Equal(t, schema.Value.Type, "string")
+	})
+}
diff --git a/openapi3/issue657_test.go b/openapi3/issue657_test.go
new file mode 100644
index 0000000..195ccd1
--- /dev/null
+++ b/openapi3/issue657_test.go
@@ -0,0 +1,79 @@
+package openapi3_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func TestOneOf_Warning_Errors(t *testing.T) {
+	t.Parallel()
+
+	loader := openapi3.NewLoader()
+	spec := `
+components:
+  schemas:
+    Something:
+      type: object
+      properties:
+        field:
+          title: Some field
+          oneOf:
+            - title: First rule
+              type: string
+              minLength: 10
+              maxLength: 10
+            - title: Second rule
+              type: string
+              minLength: 15
+              maxLength: 15
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	tests := [...]struct {
+		name     string
+		value    string
+		checkErr require.ErrorAssertionFunc
+	}{
+		{
+			name:     "valid value",
+			value:    "ABCDE01234",
+			checkErr: require.NoError,
+		},
+		{
+			name:     "valid value",
+			value:    "ABCDE0123456789",
+			checkErr: require.NoError,
+		},
+		{
+			name:  "no valid value",
+			value: "ABCDE",
+			checkErr: func(t require.TestingT, err error, i ...interface{}) {
+				require.Equal(t, "doesn't match schema due to: minimum string length is 10\nSchema:\n  {\n    \"maxLength\": 10,\n    \"minLength\": 10,\n    \"title\": \"First rule\",\n    \"type\": \"string\"\n  }\n\nValue:\n  \"ABCDE\"\n Or minimum string length is 15\nSchema:\n  {\n    \"maxLength\": 15,\n    \"minLength\": 15,\n    \"title\": \"Second rule\",\n    \"type\": \"string\"\n  }\n\nValue:\n  \"ABCDE\"\n", err.Error())
+
+				wErr := &openapi3.MultiError{}
+				require.ErrorAs(t, err, wErr)
+
+				require.Len(t, *wErr, 2)
+
+				require.Equal(t, "minimum string length is 10", (*wErr)[0].(*openapi3.SchemaError).Reason)
+				require.Equal(t, "minimum string length is 15", (*wErr)[1].(*openapi3.SchemaError).Reason)
+			},
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+
+			err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(test.value)
+
+			test.checkErr(t, err)
+		})
+	}
+}
diff --git a/openapi3/loader_test.go b/openapi3/loader_test.go
index 684e1b4..e792767 100644
--- a/openapi3/loader_test.go
+++ b/openapi3/loader_test.go
@@ -263,6 +263,29 @@ func TestLoadWithReferenceInReference(t *testing.T) {
 	require.Equal(t, "string", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
 }
 
+func TestLoadWithRecursiveReferenceInLocalReferenceInParentSubdir(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	doc, err := loader.LoadFromFile("testdata/refInLocalRefInParentsSubdir/spec/openapi.json")
+	require.NoError(t, err)
+	require.NotNil(t, doc)
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+	require.Equal(t, "object", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
+}
+
+func TestLoadWithRecursiveReferenceInRefrerenceInLocalReference(t *testing.T) {
+	loader := NewLoader()
+	loader.IsExternalRefsAllowed = true
+	doc, err := loader.LoadFromFile("testdata/refInLocalRef/openapi.json")
+	require.NoError(t, err)
+	require.NotNil(t, doc)
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+	require.Equal(t, "integer", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Type)
+	require.Equal(t, "int64", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Format)
+}
+
 func TestLoadWithReferenceInReferenceInProperty(t *testing.T) {
 	loader := NewLoader()
 	loader.IsExternalRefsAllowed = true
diff --git a/openapi3/schema_formats_test.go b/openapi3/schema_formats_test.go
index 5cceb8c..3899fab 100644
--- a/openapi3/schema_formats_test.go
+++ b/openapi3/schema_formats_test.go
@@ -2,8 +2,10 @@ package openapi3
 
 import (
 	"context"
+	"errors"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -58,3 +60,48 @@ func TestIssue430(t *testing.T) {
 		}
 	}
 }
+
+func TestFormatCallback_WrapError(t *testing.T) {
+	var errSomething = errors.New("something error")
+
+	DefineStringFormatCallback("foobar", func(value string) error {
+		return errSomething
+	})
+
+	s := &Schema{Format: "foobar"}
+	err := s.VisitJSONString("blablabla")
+
+	assert.ErrorIs(t, err, errSomething)
+
+	delete(SchemaStringFormats, "foobar")
+}
+
+func TestReversePathInMessageSchemaError(t *testing.T) {
+	DefineIPv4Format()
+
+	SchemaErrorDetailsDisabled = true
+
+	const spc = `
+components:
+  schemas:
+    Something:
+      type: object
+      properties:
+        ip:
+          type: string
+          format: ipv4
+`
+	l := NewLoader()
+
+	doc, err := l.LoadFromData([]byte(spc))
+	require.NoError(t, err)
+
+	err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
+		`ip`: `123.0.0.11111`,
+	})
+
+	require.EqualError(t, err, `Error at "/ip": Not an IP address`)
+
+	delete(SchemaStringFormats, "ipv4")
+	SchemaErrorDetailsDisabled = false
+}
diff --git a/openapi3/schema_issue289_test.go b/openapi3/schema_issue289_test.go
index 6ab6b63..56d1d45 100644
--- a/openapi3/schema_issue289_test.go
+++ b/openapi3/schema_issue289_test.go
@@ -35,5 +35,5 @@ openapi: "3.0.1"
 		"name":    "kin-openapi",
 		"address": "127.0.0.1",
 	})
-	require.EqualError(t, err, ErrOneOfConflict.Error())
+	require.ErrorIs(t, err, ErrOneOfConflict)
 }
diff --git a/openapi3/schema_oneOf_test.go b/openapi3/schema_oneOf_test.go
index 7faf268..d3e689d 100644
--- a/openapi3/schema_oneOf_test.go
+++ b/openapi3/schema_oneOf_test.go
@@ -3,6 +3,7 @@ package openapi3
 import (
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -134,3 +135,50 @@ func TestVisitJSON_OneOf_BadDescriminatorType(t *testing.T) {
 	})
 	require.EqualError(t, err, "descriminator value is not a string")
 }
+
+func TestVisitJSON_OneOf_Path(t *testing.T) {
+	t.Parallel()
+
+	loader := NewLoader()
+	spc := `
+components:
+  schemas:
+    Something:
+      type: object
+      properties:
+        first:
+          type: object
+          properties:
+            second:
+              type: object
+              properties:
+                third:
+                  oneOf:
+                   - title: First rule
+                     type: string
+                     minLength: 5
+                     maxLength: 5
+                   - title: Second rule
+                     type: string
+                     minLength: 10
+                     maxLength: 10
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(spc))
+	require.NoError(t, err)
+
+	err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
+		"first": map[string]interface{}{
+			"second": map[string]interface{}{
+				"third": "123456789",
+			},
+		},
+	})
+
+	assert.Contains(t, err.Error(), `Error at "/first/second/third"`)
+
+	var sErr *SchemaError
+
+	assert.ErrorAs(t, err, &sErr)
+	assert.Equal(t, []string{"first", "second", "third"}, sErr.JSONPointer())
+}
diff --git a/openapi3/schema_validation_settings_test.go b/openapi3/schema_validation_settings_test.go
new file mode 100644
index 0000000..db52d3b
--- /dev/null
+++ b/openapi3/schema_validation_settings_test.go
@@ -0,0 +1,36 @@
+package openapi3_test
+
+import (
+	"fmt"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func ExampleSetSchemaErrorMessageCustomizer() {
+	loader := openapi3.NewLoader()
+	spc := `
+components:
+  schemas:
+    Something:
+      type: object
+      properties:
+        field:
+          title: Some field
+          type: string
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(spc))
+	if err != nil {
+		panic(err)
+	}
+
+	opt := openapi3.SetSchemaErrorMessageCustomizer(func(err *openapi3.SchemaError) string {
+		return fmt.Sprintf(`field "%s" should be string`, err.Schema.Title)
+	})
+
+	err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(123, opt)
+
+	fmt.Println(err.Error())
+
+	// Output: field "Some field" should be string
+}
diff --git a/openapi3/testdata/issue652/definitions.yml b/openapi3/testdata/issue652/definitions.yml
new file mode 100644
index 0000000..98ef692
--- /dev/null
+++ b/openapi3/testdata/issue652/definitions.yml
@@ -0,0 +1,4 @@
+components:
+    schemas:
+        TestSchema:
+            type: string
diff --git a/openapi3/testdata/issue652/nested/schema.yml b/openapi3/testdata/issue652/nested/schema.yml
new file mode 100644
index 0000000..ef321a1
--- /dev/null
+++ b/openapi3/testdata/issue652/nested/schema.yml
@@ -0,0 +1,4 @@
+components:
+    schemas:
+        ReferenceToParentDirectory:
+            $ref: "../definitions.yml#/components/schemas/TestSchema"
diff --git a/openapi3/testdata/refInLocalRef/messages/data.json b/openapi3/testdata/refInLocalRef/messages/data.json
new file mode 100644
index 0000000..cfdc18e
--- /dev/null
+++ b/openapi3/testdata/refInLocalRef/messages/data.json
@@ -0,0 +1,12 @@
+{
+  "type": "object",
+  "properties": {
+    "id": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "ref_prop_part": {
+      "$ref": "./dataPart.json"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRef/messages/dataPart.json b/openapi3/testdata/refInLocalRef/messages/dataPart.json
new file mode 100644
index 0000000..9ecb585
--- /dev/null
+++ b/openapi3/testdata/refInLocalRef/messages/dataPart.json
@@ -0,0 +1,9 @@
+{
+  "type": "object",
+  "properties": {
+    "idPart": {
+      "type": "integer",
+      "format": "int64"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRef/messages/request.json b/openapi3/testdata/refInLocalRef/messages/request.json
new file mode 100644
index 0000000..7225ff1
--- /dev/null
+++ b/openapi3/testdata/refInLocalRef/messages/request.json
@@ -0,0 +1,11 @@
+{
+  "type": "object",
+  "required": [
+    "definition_reference"
+  ],
+  "properties": {
+    "definition_reference": {
+      "$ref": "./data.json"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRef/messages/response.json b/openapi3/testdata/refInLocalRef/messages/response.json
new file mode 100644
index 0000000..b636f52
--- /dev/null
+++ b/openapi3/testdata/refInLocalRef/messages/response.json
@@ -0,0 +1,9 @@
+{
+  "type": "object",
+  "properties": {
+    "id": {
+      "type": "integer",
+      "format": "int32"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRef/openapi.json b/openapi3/testdata/refInLocalRef/openapi.json
new file mode 100644
index 0000000..f0c9915
--- /dev/null
+++ b/openapi3/testdata/refInLocalRef/openapi.json
@@ -0,0 +1,46 @@
+{
+  "openapi": "3.0.3",
+  "info": {
+    "title": "Reference in reference example",
+    "version": "1.0.0"
+  },
+  "paths": {
+    "/api/test/ref/in/ref": {
+      "post": {
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "type": "object",
+                "properties" : {
+                  "data": {
+                    "$ref": "#/components/schemas/Request"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "Successful response",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "messages/response.json"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "Request": {
+        "$ref": "messages/request.json"
+      }
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRefInParentsSubdir/messages/data.json b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/data.json
new file mode 100644
index 0000000..cfdc18e
--- /dev/null
+++ b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/data.json
@@ -0,0 +1,12 @@
+{
+  "type": "object",
+  "properties": {
+    "id": {
+      "type": "integer",
+      "format": "int32"
+    },
+    "ref_prop_part": {
+      "$ref": "./dataPart.json"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRefInParentsSubdir/messages/dataPart.json b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/dataPart.json
new file mode 100644
index 0000000..9ecb585
--- /dev/null
+++ b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/dataPart.json
@@ -0,0 +1,9 @@
+{
+  "type": "object",
+  "properties": {
+    "idPart": {
+      "type": "integer",
+      "format": "int64"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRefInParentsSubdir/messages/request.json b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/request.json
new file mode 100644
index 0000000..7225ff1
--- /dev/null
+++ b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/request.json
@@ -0,0 +1,11 @@
+{
+  "type": "object",
+  "required": [
+    "definition_reference"
+  ],
+  "properties": {
+    "definition_reference": {
+      "$ref": "./data.json"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRefInParentsSubdir/messages/response.json b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/response.json
new file mode 100644
index 0000000..b636f52
--- /dev/null
+++ b/openapi3/testdata/refInLocalRefInParentsSubdir/messages/response.json
@@ -0,0 +1,9 @@
+{
+  "type": "object",
+  "properties": {
+    "id": {
+      "type": "integer",
+      "format": "int32"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInLocalRefInParentsSubdir/spec/openapi.json b/openapi3/testdata/refInLocalRefInParentsSubdir/spec/openapi.json
new file mode 100644
index 0000000..0bf9bd3
--- /dev/null
+++ b/openapi3/testdata/refInLocalRefInParentsSubdir/spec/openapi.json
@@ -0,0 +1,46 @@
+{
+  "openapi": "3.0.3",
+  "info": {
+    "title": "Reference in reference example",
+    "version": "1.0.0"
+  },
+  "paths": {
+    "/api/test/ref/in/ref": {
+      "post": {
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "../messages/request.json"
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "Successful response",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object",
+                  "properties": {
+                    "ref_prop": {
+                      "$ref": "#/components/schemas/Data"
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "Data": {
+        "$ref": "../messages/data.json"
+      }
+    }
+  }
+}
diff --git a/openapi3/testdata/schema618.yml b/openapi3/testdata/schema618.yml
new file mode 100644
index 0000000..1ab4000
--- /dev/null
+++ b/openapi3/testdata/schema618.yml
@@ -0,0 +1,62 @@
+components:
+  schemas:
+    Account:
+      required:
+        - name
+        - nature
+      type: object
+      properties:
+        id:
+          type: string
+        name:
+          type: string
+        description:
+          type: string
+        type:
+          type: string
+          enum:
+            - assets
+            - liabilities
+        nature:
+          type: string
+          enum:
+            - asset
+            - liability
+    Record:
+      required:
+        - account
+        - concept
+      type: object
+      properties:
+        account:
+          $ref: "#/components/schemas/Account"
+        concept:
+          type: string
+        partial:
+          type: number
+        credit:
+          type: number
+        debit:
+          type: number
+    JournalEntry:
+      required:
+        - type
+        - creationDate
+        - records
+      type: object
+      properties:
+        id:
+          type: string
+        type:
+          type: string
+          enum:
+            - daily
+            - ingress
+            - egress
+        creationDate:
+          type: string
+          format: date
+        records:
+          type: array
+          items:
+            $ref: "#/components/schemas/Record"
diff --git a/openapi3filter/issue201_test.go b/openapi3filter/issue201_test.go
index 8db8620..8b2b99d 100644
--- a/openapi3filter/issue201_test.go
+++ b/openapi3filter/issue201_test.go
@@ -94,7 +94,7 @@ paths:
 		},
 
 		"invalid required header": {
-			err: `response header "X-Blup" doesn't match the schema: string doesn't match the regular expression "^blup$"`,
+			err: `response header "X-Blup" doesn't match the schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
 			headers: map[string]string{
 				"X-Blip": "blip",
 				"x-blop": "blop",
diff --git a/openapi3filter/issue625_test.go b/openapi3filter/issue625_test.go
new file mode 100644
index 0000000..d9e5bae
--- /dev/null
+++ b/openapi3filter/issue625_test.go
@@ -0,0 +1,123 @@
+package openapi3filter_test
+
+import (
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/openapi3filter"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestIssue625(t *testing.T) {
+
+	anyOfArraySpec := `
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: Sample API
+paths:
+ /items:
+  get:
+    description: Returns a list of stuff
+    parameters:
+    - description: test object
+      explode: false
+      in: query
+      name: test
+      required: false
+      schema:
+        type: array
+        items:
+         anyOf:
+          - type: integer
+          - type: boolean
+    responses:
+      '200':
+        description: Successful response
+`[1:]
+
+	oneOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "oneOf")
+
+	allOfArraySpec := strings.ReplaceAll(strings.ReplaceAll(anyOfArraySpec, "anyOf", "allOf"),
+		"type: boolean", "type: number")
+
+	tests := []struct {
+		name   string
+		spec   string
+		req    string
+		errStr string
+	}{
+		{
+			name: "success anyof object array",
+			spec: anyOfArraySpec,
+			req:  "/items?test=3,7",
+		},
+		{
+			name:   "failed anyof object array",
+			spec:   anyOfArraySpec,
+			req:    "/items?test=s1,s2",
+			errStr: `parameter "test" in query has an error: path 0: value s1: an invalid boolean: invalid syntax`,
+		},
+
+		{
+			name: "success allof object array",
+			spec: allOfArraySpec,
+			req:  `/items?test=1,3`,
+		},
+		{
+			name:   "failed allof object array",
+			spec:   allOfArraySpec,
+			req:    `/items?test=1.2,3.1`,
+			errStr: `parameter "test" in query has an error: Error at "/0": value "1.2" must be an integer`,
+		},
+		{
+			name: "success oneof object array",
+			spec: oneOfArraySpec,
+			req:  `/items?test=true,3`,
+		},
+		{
+			name:   "faled oneof object array",
+			spec:   oneOfArraySpec,
+			req:    `/items?test="val1","val2"`,
+			errStr: `parameter "test" in query has an error: item 0: decoding oneOf failed: 0 schemas matched`,
+		},
+	}
+
+	for _, testcase := range tests {
+		t.Run(testcase.name, func(t *testing.T) {
+			loader := openapi3.NewLoader()
+			ctx := loader.Context
+
+			doc, err := loader.LoadFromData([]byte(testcase.spec))
+			require.NoError(t, err)
+
+			err = doc.Validate(ctx)
+			require.NoError(t, err)
+
+			router, err := gorillamux.NewRouter(doc)
+			require.NoError(t, err)
+			httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
+			require.NoError(t, err)
+
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
+
+			requestValidationInput := &openapi3filter.RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+			}
+			err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
+			if testcase.errStr == "" {
+				require.NoError(t, err)
+			} else {
+				require.Contains(t, err.Error(), testcase.errStr)
+			}
+		},
+		)
+	}
+}
diff --git a/openapi3filter/issue639_test.go b/openapi3filter/issue639_test.go
new file mode 100644
index 0000000..2caf1bd
--- /dev/null
+++ b/openapi3filter/issue639_test.go
@@ -0,0 +1,100 @@
+package openapi3filter
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestIssue639(t *testing.T) {
+	loader := openapi3.NewLoader()
+	ctx := loader.Context
+	spec := `
+    openapi: 3.0.0
+    info:
+     version: 1.0.0
+     title: Sample API
+    paths:
+     /items:
+      put:
+        requestBody:
+         content:
+          application/json:
+            schema:
+              properties:
+                testWithdefault:
+                  default: false
+                  type: boolean
+                testNoDefault:
+                  type: boolean
+              type: object
+        responses:
+          '200':
+           description: Successful respons
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	err = doc.Validate(ctx)
+	require.NoError(t, err)
+
+	router, err := gorillamux.NewRouter(doc)
+	require.NoError(t, err)
+
+	tests := []struct {
+		name               string
+		options            *Options
+		expectedDefaultVal interface{}
+	}{
+		{
+			name: "no defaults are added to requests",
+			options: &Options{
+				SkipSettingDefaults: true,
+			},
+			expectedDefaultVal: nil,
+		},
+
+		{
+			name:               "defaults are added to requests",
+			expectedDefaultVal: false,
+		},
+	}
+
+	for _, testcase := range tests {
+		t.Run(testcase.name, func(t *testing.T) {
+			body := "{\"testNoDefault\": true}"
+			httpReq, err := http.NewRequest(http.MethodPut, "/items", strings.NewReader(body))
+			require.NoError(t, err)
+			httpReq.Header.Set("Content-Type", "application/json")
+			require.NoError(t, err)
+
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
+
+			requestValidationInput := &RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+				Options:    testcase.options,
+			}
+			err = ValidateRequest(ctx, requestValidationInput)
+			require.NoError(t, err)
+			bodyAfterValidation, err := ioutil.ReadAll(httpReq.Body)
+			require.NoError(t, err)
+
+			raw := map[string]interface{}{}
+			err = json.Unmarshal(bodyAfterValidation, &raw)
+			require.NoError(t, err)
+			require.Equal(t, testcase.expectedDefaultVal,
+				raw["testWithdefault"], "default value must not be included")
+		})
+	}
+}
diff --git a/openapi3filter/issue641_test.go b/openapi3filter/issue641_test.go
new file mode 100644
index 0000000..9a29642
--- /dev/null
+++ b/openapi3filter/issue641_test.go
@@ -0,0 +1,109 @@
+package openapi3filter_test
+
+import (
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/openapi3filter"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestIssue641(t *testing.T) {
+
+	anyOfSpec := `
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: Sample API
+paths:
+ /items:
+  get:
+    description: Returns a list of stuff
+    parameters:
+    - description: test object
+      explode: false
+      in: query
+      name: test
+      required: false
+      schema:
+        anyOf:
+        - pattern: "^[0-9]{1,4}$"
+        - pattern: "^[0-9]{1,4}$"
+        type: string
+    responses:
+      '200':
+        description: Successful response
+`[1:]
+
+	allOfSpec := strings.ReplaceAll(anyOfSpec, "anyOf", "allOf")
+
+	tests := []struct {
+		name   string
+		spec   string
+		req    string
+		errStr string
+	}{
+
+		{
+			name: "success anyof pattern",
+			spec: anyOfSpec,
+			req:  "/items?test=51",
+		},
+		{
+			name:   "failed anyof pattern",
+			spec:   anyOfSpec,
+			req:    "/items?test=999999",
+			errStr: `parameter "test" in query has an error: Doesn't match schema "anyOf"`,
+		},
+
+		{
+			name: "success allof pattern",
+			spec: allOfSpec,
+			req:  `/items?test=51`,
+		},
+		{
+			name:   "failed allof pattern",
+			spec:   allOfSpec,
+			req:    `/items?test=999999`,
+			errStr: `parameter "test" in query has an error: string "999999" doesn't match the regular expression "^[0-9]{1,4}$"`,
+		},
+	}
+
+	for _, testcase := range tests {
+		t.Run(testcase.name, func(t *testing.T) {
+			loader := openapi3.NewLoader()
+			ctx := loader.Context
+
+			doc, err := loader.LoadFromData([]byte(testcase.spec))
+			require.NoError(t, err)
+
+			err = doc.Validate(ctx)
+			require.NoError(t, err)
+
+			router, err := gorillamux.NewRouter(doc)
+			require.NoError(t, err)
+			httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
+			require.NoError(t, err)
+
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
+
+			requestValidationInput := &openapi3filter.RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+			}
+			err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
+			if testcase.errStr == "" {
+				require.NoError(t, err)
+			} else {
+				require.Contains(t, err.Error(), testcase.errStr)
+			}
+		},
+		)
+	}
+}
diff --git a/openapi3filter/options_test.go b/openapi3filter/options_test.go
new file mode 100644
index 0000000..a95b6bb
--- /dev/null
+++ b/openapi3filter/options_test.go
@@ -0,0 +1,82 @@
+package openapi3filter_test
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/openapi3filter"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func ExampleOptions_WithCustomSchemaErrorFunc() {
+	const spec = `
+openapi: 3.0.0
+info:
+  title: 'Validator'
+  version: 0.0.1
+paths:
+  /some:
+    post:
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                field:
+                  title: Some field
+                  type: integer
+      responses:
+        '200':
+          description: Created
+`
+
+	loader := openapi3.NewLoader()
+	doc, err := loader.LoadFromData([]byte(spec))
+	if err != nil {
+		panic(err)
+	}
+
+	if err = doc.Validate(loader.Context); err != nil {
+		panic(err)
+	}
+
+	router, err := gorillamux.NewRouter(doc)
+	if err != nil {
+		panic(err)
+	}
+
+	opts := &openapi3filter.Options{}
+
+	opts.WithCustomSchemaErrorFunc(func(err *openapi3.SchemaError) string {
+		return fmt.Sprintf(`field "%s" must be an integer`, err.Schema.Title)
+	})
+
+	req, err := http.NewRequest(http.MethodPost, "/some", strings.NewReader(`{"field":"not integer"}`))
+	if err != nil {
+		panic(err)
+	}
+
+	req.Header.Add("Content-Type", "application/json")
+
+	route, pathParams, err := router.FindRoute(req)
+	if err != nil {
+		panic(err)
+	}
+
+	validationInput := &openapi3filter.RequestValidationInput{
+		Request:    req,
+		PathParams: pathParams,
+		Route:      route,
+		Options:    opts,
+	}
+	err = openapi3filter.ValidateRequest(context.Background(), validationInput)
+
+	fmt.Println(err.Error())
+
+	// Output: request body has an error: doesn't match the schema: field "Some field" must be an integer
+}
diff --git a/openapi3filter/req_resp_encoder_test.go b/openapi3filter/req_resp_encoder_test.go
new file mode 100644
index 0000000..11fe2af
--- /dev/null
+++ b/openapi3filter/req_resp_encoder_test.go
@@ -0,0 +1,43 @@
+package openapi3filter
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestRegisterAndUnregisterBodyEncoder(t *testing.T) {
+	var encoder BodyEncoder
+	encoder = func(body interface{}) (data []byte, err error) {
+		return []byte(strings.Join(body.([]string), ",")), nil
+	}
+	contentType := "text/csv"
+	h := make(http.Header)
+	h.Set(headerCT, contentType)
+
+	originalEncoder := RegisteredBodyEncoder(contentType)
+	require.Nil(t, originalEncoder)
+
+	RegisterBodyEncoder(contentType, encoder)
+	require.Equal(t, fmt.Sprintf("%v", encoder), fmt.Sprintf("%v", RegisteredBodyEncoder(contentType)))
+
+	body := []string{"foo", "bar"}
+	got, err := encodeBody(body, contentType)
+
+	require.NoError(t, err)
+	require.Equal(t, []byte("foo,bar"), got)
+
+	UnregisterBodyEncoder(contentType)
+
+	originalEncoder = RegisteredBodyEncoder(contentType)
+	require.Nil(t, originalEncoder)
+
+	_, err = encodeBody(body, contentType)
+	require.Equal(t, &ParseError{
+		Kind:   KindUnsupportedFormat,
+		Reason: prefixUnsupportedCT + ` "text/csv"`,
+	}, err)
+}
diff --git a/openapi3filter/unpack_errors_test.go b/openapi3filter/unpack_errors_test.go
index 552e20e..0ee48ad 100644
--- a/openapi3filter/unpack_errors_test.go
+++ b/openapi3filter/unpack_errors_test.go
@@ -81,7 +81,7 @@ func Example() {
 	// Output:
 	// ===== Start New Error =====
 	// @body.name:
-	// 	Error at "/name": Field must be set to string or not be present
+	// 	Error at "/name": field must be set to string or not be present
 	// Schema:
 	//   {
 	//     "example": "doggie",
@@ -93,7 +93,7 @@ func Example() {
 	//
 	// ===== Start New Error =====
 	// @body.status:
-	// 	Error at "/status": value is not one of the allowed values
+	// 	Error at "/status": value "invalidStatus" is not one of the allowed values
 	// Schema:
 	//   {
 	//     "description": "pet status in the store",
diff --git a/openapi3filter/validate_request_test.go b/openapi3filter/validate_request_test.go
index 957b692..450ee59 100644
--- a/openapi3filter/validate_request_test.go
+++ b/openapi3filter/validate_request_test.go
@@ -59,6 +59,9 @@ paths:
               properties:
                 subCategory:
                   type: string
+                category:
+                  type: string
+                  default: Sweets
       responses:
         '201':
           description: Created
@@ -95,6 +98,7 @@ components:
 
 	type testRequestBody struct {
 		SubCategory string `json:"subCategory"`
+		Category    string `json:"category,omitempty"`
 	}
 	type args struct {
 		requestBody *testRequestBody
@@ -102,18 +106,30 @@ components:
 		apiKey      string
 	}
 	tests := []struct {
-		name        string
-		args        args
-		expectedErr error
+		name                 string
+		args                 args
+		expectedModification bool
+		expectedErr          error
 	}{
 		{
-			name: "Valid request",
+			name: "Valid request with all fields set",
+			args: args{
+				requestBody: &testRequestBody{SubCategory: "Chocolate", Category: "Food"},
+				url:         "/category?category=cookies",
+				apiKey:      "SomeKey",
+			},
+			expectedModification: false,
+			expectedErr:          nil,
+		},
+		{
+			name: "Valid request without certain fields",
 			args: args{
 				requestBody: &testRequestBody{SubCategory: "Chocolate"},
 				url:         "/category?category=cookies",
 				apiKey:      "SomeKey",
 			},
-			expectedErr: nil,
+			expectedModification: true,
+			expectedErr:          nil,
 		},
 		{
 			name: "Invalid operation params",
@@ -122,7 +138,8 @@ components:
 				url:         "/category?invalidCategory=badCookie",
 				apiKey:      "SomeKey",
 			},
-			expectedErr: &RequestError{},
+			expectedModification: false,
+			expectedErr:          &RequestError{},
 		},
 		{
 			name: "Invalid request body",
@@ -131,7 +148,8 @@ components:
 				url:         "/category?category=cookies",
 				apiKey:      "SomeKey",
 			},
-			expectedErr: &RequestError{},
+			expectedModification: false,
+			expectedErr:          &RequestError{},
 		},
 		{
 			name: "Invalid security",
@@ -140,7 +158,8 @@ components:
 				url:         "/category?category=cookies",
 				apiKey:      "",
 			},
-			expectedErr: &SecurityRequirementsError{},
+			expectedModification: false,
+			expectedErr:          &SecurityRequirementsError{},
 		},
 		{
 			name: "Invalid request body and security",
@@ -149,16 +168,19 @@ components:
 				url:         "/category?category=cookies",
 				apiKey:      "",
 			},
-			expectedErr: &SecurityRequirementsError{},
+			expectedModification: false,
+			expectedErr:          &SecurityRequirementsError{},
 		},
 	}
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			var requestBody io.Reader
+			var originalBodySize int
 			if tc.args.requestBody != nil {
 				testingBody, err := json.Marshal(tc.args.requestBody)
 				require.NoError(t, err)
 				requestBody = bytes.NewReader(testingBody)
+				originalBodySize = len(testingBody)
 			}
 			req, err := http.NewRequest(http.MethodPost, tc.args.url, requestBody)
 			require.NoError(t, err)
@@ -180,6 +202,16 @@ components:
 			}
 			err = ValidateRequest(context.Background(), validationInput)
 			assert.IsType(t, tc.expectedErr, err, "ValidateRequest(): error = %v, expectedError %v", err, tc.expectedErr)
+			if tc.expectedErr != nil {
+				return
+			}
+			body, err := io.ReadAll(validationInput.Request.Body)
+			contentLen := int(validationInput.Request.ContentLength)
+			bodySize := len(body)
+			assert.NoError(t, err, "unable to read request body: %v", err)
+			assert.Equal(t, contentLen, bodySize, "expect ContentLength %d to equal body size %d", contentLen, bodySize)
+			bodyModified := originalBodySize != bodySize
+			assert.Equal(t, bodyModified, tc.expectedModification, "expect request body modification happened: %t, expected %t", bodyModified, tc.expectedModification)
 		})
 	}
 }
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index c1d4630..6fee135 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -244,11 +244,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"available,sold\" is not one of the allowed values",
 			wantErrSchemaPath:   "/0",
 			wantErrSchemaValue:  "available,sold",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "value is not one of the allowed values",
+				Title: "value \"available,sold\" is not one of the allowed values",
 				Detail: "value available,sold at /0 must be one of: available, pending, sold; " +
 					// TODO: do we really want to use this heuristic to guess
 					//  that they're using the wrong serialization?
@@ -262,11 +262,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				Detail: "value watdis at /1 must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Parameter: "status"}},
 		},
@@ -278,11 +278,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "kind",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"fish,with,commas\" is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "fish,with,commas",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"fish,with,commas\" is not one of the allowed values",
 				Detail: "value fish,with,commas at /1 must be one of: dog, cat, turtle, bird,with,commas",
 				// No 'perhaps you intended' because its the right serialization format
 				Source: &ValidationErrorSource{Parameter: "kind"}},
@@ -304,11 +304,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "x-environment",
 			wantErrParamIn:      "header",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaPath:   "/",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				Detail: "value watdis at / must be one of: demo, prod",
 				Source: &ValidationErrorSource{Parameter: "x-environment"}},
 		},
@@ -323,11 +323,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaValue:  "watdis",
 			wantErrSchemaPath:   "/status",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				Detail: "value watdis at /status must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Pointer: "/status"}},
 		},
@@ -379,13 +379,13 @@ func getValidationTests(t *testing.T) []*validationTest {
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "Field must be set to array or not be present",
+			wantErrSchemaReason: "field must be set to array or not be present",
 			wantErrSchemaPath:   "/photoUrls",
 			wantErrSchemaValue:  "string",
 			// TODO: this shouldn't say "or not be present", but this requires recursively resolving
 			//  innerErr.JSONPointer() against e.RequestBody.Content["application/json"].Schema.Value (.Required, .Properties)
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "Field must be set to array or not be present",
+				Title:  "field must be set to array or not be present",
 				Source: &ValidationErrorSource{Pointer: "/photoUrls"}},
 		},
 		{
@@ -659,7 +659,7 @@ func TestValidationHandler_ServeHTTP(t *testing.T) {
 		body, err := ioutil.ReadAll(resp.Body)
 		require.NoError(t, err)
 		require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
-		require.Equal(t, "[422][][] Field must be set to array or not be present [source pointer=/photoUrls]", string(body))
+		require.Equal(t, "[422][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
 	})
 }
 
@@ -701,6 +701,6 @@ func TestValidationHandler_Middleware(t *testing.T) {
 		body, err := ioutil.ReadAll(resp.Body)
 		require.NoError(t, err)
 		require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
-		require.Equal(t, "[422][][] Field must be set to array or not be present [source pointer=/photoUrls]", string(body))
+		require.Equal(t, "[422][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
 	})
 }
diff --git a/routers/gorillamux/example_test.go b/routers/gorillamux/example_test.go
index 33ce98e..2ca3225 100644
--- a/routers/gorillamux/example_test.go
+++ b/routers/gorillamux/example_test.go
@@ -53,7 +53,7 @@ func Example() {
 	err = openapi3filter.ValidateResponse(ctx, responseValidationInput)
 	fmt.Println(err)
 	// Output:
-	// response body doesn't match the schema: Field must be set to string or not be present
+	// response body doesn't match the schema: field must be set to string or not be present
 	// Schema:
 	//   {
 	//     "type": "string"