Repository: getkin/kin-openapi
Pull request #698 (2022-12-12 02:15:32)

openapi3: patch YAML serialization of dates

fenollp: I'm down with this but aren't there more places this should be handled? Such as the `examples` field?

Also, have you looked at a way to fix this upstream and what were your findings?

Thanks!
cosnicolaou: It's clearly an ugly fix and I'm open to suggestions for a betterone! It can affect other fields for sure, but I'm not sure it matters for those, then again, I'm not an experienced openapi user. This particular problem seems very specific to openapi, that is, the spec contains a literal date which gets turned into a date-time by json/yaml marshal/unmarshaling which is considered invalid since the format field is a date. Another option would be to change the validation to treat the 00:00:00Z suffix as not being present, or to issue a warning rather than an error. An alternative is to edit the original input specification to use a string rather than a literal, but that is also far from ideal since it's tedious to find all of the occurrences that need to be fixed.

When you say 'aren't there more places' - do you have any that you're concerned about?
cosnicolaou: As an aside, I just wrote a small package to allow 'walking' of an openapi3.T, https://github.com/cosnicolaou/openapi. If you wish, it's probably better to integrate the 'walk' portion (not necessarily the transformations) into your core package. 

Patch:
diff --git a/openapi3/callback.go b/openapi3/callback.go
index 6246d6d..62cea72 100644
--- a/openapi3/callback.go
+++ b/openapi3/callback.go
@@ -26,7 +26,7 @@ func (c Callbacks) JSONLookup(token string) (interface{}, error) {
 }
 
 // Callback is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callbackObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
 type Callback map[string]*PathItem
 
 // Validate returns an error if Callback does not comply with the OpenAPI spec.
diff --git a/openapi3/components.go b/openapi3/components.go
index 16b39f3..8abc3fe 100644
--- a/openapi3/components.go
+++ b/openapi3/components.go
@@ -10,7 +10,7 @@ import (
 )
 
 // Components is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#componentsObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
 type Components struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go
index 8eb2960..8ab344a 100644
--- a/openapi3/discriminator.go
+++ b/openapi3/discriminator.go
@@ -7,7 +7,7 @@ import (
 )
 
 // Discriminator is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminatorObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object
 type Discriminator struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/doc.go b/openapi3/doc.go
index fc2735c..41c9965 100644
--- a/openapi3/doc.go
+++ b/openapi3/doc.go
@@ -1,4 +1,4 @@
 // Package openapi3 parses and writes OpenAPI 3 specification documents.
 //
-// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md
+// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
 package openapi3
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index 082d3f2..003833e 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -9,7 +9,7 @@ import (
 )
 
 // Encoding is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encodingObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object
 type Encoding struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/example.go b/openapi3/example.go
index e75d4d3..f4cbfc0 100644
--- a/openapi3/example.go
+++ b/openapi3/example.go
@@ -28,7 +28,7 @@ func (e Examples) JSONLookup(token string) (interface{}, error) {
 }
 
 // Example is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#exampleObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object
 type Example struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/header.go b/openapi3/header.go
index aefaa06..454fad5 100644
--- a/openapi3/header.go
+++ b/openapi3/header.go
@@ -28,7 +28,7 @@ func (h Headers) JSONLookup(token string) (interface{}, error) {
 }
 
 // Header is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#headerObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object
 type Header struct {
 	Parameter
 }
diff --git a/openapi3/info.go b/openapi3/info.go
index c67f73a..7207609 100644
--- a/openapi3/info.go
+++ b/openapi3/info.go
@@ -8,7 +8,7 @@ import (
 )
 
 // Info is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#infoObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object
 type Info struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
@@ -58,7 +58,7 @@ func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error
 }
 
 // Contact is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contactObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object
 type Contact struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
@@ -85,7 +85,7 @@ func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption)
 }
 
 // License is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#licenseObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object
 type License struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/link.go b/openapi3/link.go
index 1040a04..137aef3 100644
--- a/openapi3/link.go
+++ b/openapi3/link.go
@@ -28,7 +28,7 @@ func (links Links) JSONLookup(token string) (interface{}, error) {
 var _ jsonpointer.JSONPointable = (*Links)(nil)
 
 // Link is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#linkObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object
 type Link struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/loader.go b/openapi3/loader.go
index 3979e5d..4cc83c0 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -287,20 +287,21 @@ func (loader *Loader) resolveComponent(
 	path *url.URL,
 	resolved interface{},
 ) (
+	componentDoc *T,
 	componentPath *url.URL,
 	err error,
 ) {
-	if doc, ref, componentPath, err = loader.resolveRef(doc, ref, path); err != nil {
-		return nil, err
+	if componentDoc, ref, componentPath, err = loader.resolveRef(doc, ref, path); err != nil {
+		return nil, nil, err
 	}
 
 	parsedURL, err := url.Parse(ref)
 	if err != nil {
-		return nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
+		return nil, nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
 	}
 	fragment := parsedURL.Fragment
 	if !strings.HasPrefix(fragment, "/") {
-		return nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref)
+		return nil, nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref)
 	}
 
 	drill := func(cursor interface{}) (interface{}, error) {
@@ -318,20 +319,20 @@ func (loader *Loader) resolveComponent(
 		return cursor, nil
 	}
 	var cursor interface{}
-	if cursor, err = drill(doc); err != nil {
+	if cursor, err = drill(componentDoc); err != nil {
 		if path == nil {
-			return nil, err
+			return nil, nil, err
 		}
 		var err2 error
 		data, err2 := loader.readURL(path)
 		if err2 != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		if err2 = unmarshal(data, &cursor); err2 != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		if cursor, err2 = drill(cursor); err2 != nil || cursor == nil {
-			return nil, err
+			return nil, nil, err
 		}
 		err = nil
 	}
@@ -339,7 +340,7 @@ func (loader *Loader) resolveComponent(
 	switch {
 	case reflect.TypeOf(cursor) == reflect.TypeOf(resolved):
 		reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem())
-		return componentPath, nil
+		return componentDoc, componentPath, nil
 
 	case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]interface{}{}):
 		codec := func(got, expect interface{}) error {
@@ -353,12 +354,12 @@ func (loader *Loader) resolveComponent(
 			return nil
 		}
 		if err := codec(cursor, resolved); err != nil {
-			return nil, fmt.Errorf("bad data in %q", ref)
+			return nil, nil, fmt.Errorf("bad data in %q", ref)
 		}
-		return componentPath, nil
+		return componentDoc, componentPath, nil
 
 	default:
-		return nil, fmt.Errorf("bad data in %q", ref)
+		return nil, nil, fmt.Errorf("bad data in %q", ref)
 	}
 }
 
@@ -429,18 +430,6 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
 	}
 }
 
-func (loader *Loader) documentPathForRecursiveRef(current *url.URL, resolvedRef string) *url.URL {
-	if loader.rootDir == "" {
-		return current
-	}
-
-	if resolvedRef == "" {
-		return &url.URL{Path: loader.rootLocation}
-	}
-
-	return &url.URL{Path: path.Join(loader.rootDir, resolvedRef)}
-}
-
 func (loader *Loader) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) {
 	if ref != "" && ref[0] == '#' {
 		return doc, ref, path, nil
@@ -492,7 +481,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat
 			component.Value = &header
 		} else {
 			var resolved HeaderRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -500,7 +489,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat
 				return err
 			}
 			component.Value = resolved.Value
-			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	value := component.Value
@@ -540,7 +529,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum
 			component.Value = &param
 		} else {
 			var resolved ParameterRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -548,7 +537,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum
 				return err
 			}
 			component.Value = resolved.Value
-			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	value := component.Value
@@ -597,7 +586,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
 			component.Value = &requestBody
 		} else {
 			var resolved RequestBodyRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -605,7 +594,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
 				return err
 			}
 			component.Value = resolved.Value
-			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	value := component.Value
@@ -659,7 +648,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
 			component.Value = &resp
 		} else {
 			var resolved ResponseRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -667,7 +656,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
 				return err
 			}
 			component.Value = resolved.Value
-			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	value := component.Value
@@ -742,7 +731,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 			visited = append(visited, ref)
 
 			var resolved SchemaRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -750,11 +739,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 				return err
 			}
 			component.Value = resolved.Value
-			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)
+			return nil
 		}
 		if loader.visitedSchema == nil {
 			loader.visitedSchema = make(map[*Schema]struct{})
@@ -805,30 +790,6 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 	return nil
 }
 
-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:], nil
-			}
-
-			return path.Base(cur.Path), nil
-		}
-		return "", nil
-	}
-	// ref. to external file
-	if resolved.Ref != "" {
-		return resolved.Ref, nil
-	}
-
-	if loader.rootDir == "" {
-		return found.Path, nil
-	}
-
-	// found dest spec. file
-	return filepath.Rel(loader.rootDir, found.Path)
-}
-
 func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if loader.visitedSecurityScheme == nil {
@@ -852,7 +813,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme
 			component.Value = &scheme
 		} else {
 			var resolved SecuritySchemeRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -860,7 +821,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme
 				return err
 			}
 			component.Value = resolved.Value
-			_ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	return nil
@@ -889,7 +850,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
 			component.Value = &example
 		} else {
 			var resolved ExampleRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -897,7 +858,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
 				return err
 			}
 			component.Value = resolved.Value
-			_ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	return nil
@@ -916,7 +877,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 			component.Value = &resolved
 		} else {
 			var resolved CallbackRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -924,7 +885,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 				return err
 			}
 			component.Value = resolved.Value
-			documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	value := component.Value
@@ -1014,7 +975,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
 			component.Value = &link
 		} else {
 			var resolved LinkRef
-			componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
+			doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
@@ -1022,7 +983,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
 				return err
 			}
 			component.Value = resolved.Value
-			_ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
+			return nil
 		}
 	}
 	return nil
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index 74c11b7..8e3fef7 100644
--- a/openapi3/media_type.go
+++ b/openapi3/media_type.go
@@ -12,7 +12,7 @@ import (
 )
 
 // MediaType is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object
 type MediaType struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index 714f280..d9506a9 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -9,7 +9,7 @@ import (
 )
 
 // T is the root of an OpenAPI v3 document
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oasObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object
 type T struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 9124d92..c55af47 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -90,7 +90,7 @@ func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOpt
 }
 
 // Parameter is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameterObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
 type Parameter struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 5cba0a8..db83a0a 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -10,7 +10,7 @@ import (
 )
 
 // PathItem is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#pathItemObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object
 type PathItem struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/refs.go b/openapi3/refs.go
index 7311c9d..d36d562 100644
--- a/openapi3/refs.go
+++ b/openapi3/refs.go
@@ -9,7 +9,7 @@ import (
 )
 
 // Ref is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#referenceObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
 type Ref struct {
 	Ref string `json:"$ref" yaml:"$ref"`
 }
diff --git a/openapi3/request_body.go b/openapi3/request_body.go
index 225c3d3..baf7f81 100644
--- a/openapi3/request_body.go
+++ b/openapi3/request_body.go
@@ -28,7 +28,7 @@ func (r RequestBodies) JSONLookup(token string) (interface{}, error) {
 }
 
 // RequestBody is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#requestBodyObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object
 type RequestBody struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/response.go b/openapi3/response.go
index d2f907d..eaf8e57 100644
--- a/openapi3/response.go
+++ b/openapi3/response.go
@@ -13,7 +13,7 @@ import (
 )
 
 // Responses is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responsesObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
 type Responses map[string]*ResponseRef
 
 var _ jsonpointer.JSONPointable = (*Responses)(nil)
@@ -68,7 +68,7 @@ func (responses Responses) JSONLookup(token string) (interface{}, error) {
 }
 
 // Response is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responseObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
 type Response struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 67cc7dc..41ccaaf 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -12,6 +12,7 @@ import (
 	"regexp"
 	"sort"
 	"strconv"
+	"strings"
 	"unicode/utf16"
 
 	"github.com/go-openapi/jsonpointer"
@@ -111,7 +112,7 @@ func (s SchemaRefs) JSONLookup(token string) (interface{}, error) {
 }
 
 // Schema is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
 type Schema struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
@@ -180,7 +181,14 @@ func (schema *Schema) MarshalJSON() ([]byte, error) {
 
 // UnmarshalJSON sets Schema to a copy of data.
 func (schema *Schema) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, schema)
+	err := jsoninfo.UnmarshalStrictStruct(data, schema)
+	if schema.Format == "date" {
+		// This is a fix for: https://github.com/getkin/kin-openapi/issues/697
+		if eg, ok := schema.Example.(string); ok {
+			schema.Example = strings.TrimSuffix(eg, "T00:00:00Z")
+		}
+	}
+	return err
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
diff --git a/openapi3/security_requirements.go b/openapi3/security_requirements.go
index dcdad0c..3f5bd95 100644
--- a/openapi3/security_requirements.go
+++ b/openapi3/security_requirements.go
@@ -28,7 +28,7 @@ func (srs SecurityRequirements) Validate(ctx context.Context, opts ...Validation
 }
 
 // SecurityRequirement is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securityRequirementObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object
 type SecurityRequirement map[string][]string
 
 func NewSecurityRequirement() SecurityRequirement {
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index 2b3235d..83330f2 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -29,7 +29,7 @@ func (s SecuritySchemes) JSONLookup(token string) (interface{}, error) {
 var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
 
 // SecurityScheme is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securitySchemeObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object
 type SecurityScheme struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
@@ -177,7 +177,7 @@ func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption
 }
 
 // OAuthFlows is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowsObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object
 type OAuthFlows struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
@@ -234,7 +234,7 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption)
 }
 
 // OAuthFlow is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object
 type OAuthFlow struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/server.go b/openapi3/server.go
index 3047996..587e8e0 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -50,7 +50,7 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
 }
 
 // Server is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#serverObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
 type Server struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/tag.go b/openapi3/tag.go
index f151e50..b5cb7f8 100644
--- a/openapi3/tag.go
+++ b/openapi3/tag.go
@@ -32,7 +32,7 @@ func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error {
 }
 
 // Tag is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tagObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object
 type Tag struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3/xml.go b/openapi3/xml.go
index 4ed3d94..a55ff41 100644
--- a/openapi3/xml.go
+++ b/openapi3/xml.go
@@ -7,7 +7,7 @@ import (
 )
 
 // XML is specified by OpenAPI/Swagger standard version 3.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xmlObject
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object
 type XML struct {
 	ExtensionProps `json:"-" yaml:"-"`
 
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 029a84c..870651c 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -895,7 +895,14 @@ func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error)
 	}
 	switch schema.Value.Type {
 	case "integer":
-		v, err := strconv.ParseFloat(raw, 64)
+		if schema.Value.Format == "int32" {
+			v, err := strconv.ParseInt(raw, 0, 32)
+			if err != nil {
+				return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
+			}
+			return int32(v), nil
+		}
+		v, err := strconv.ParseInt(raw, 0, 64)
 		if err != nil {
 			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
 		}
@@ -999,6 +1006,7 @@ func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef,
 func init() {
 	RegisterBodyDecoder("text/plain", plainBodyDecoder)
 	RegisterBodyDecoder("application/json", jsonBodyDecoder)
+	RegisterBodyDecoder("application/json-patch+json", jsonBodyDecoder)
 	RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder)
 	RegisterBodyDecoder("application/yaml", yamlBodyDecoder)
 	RegisterBodyDecoder("application/problem+json", jsonBodyDecoder)
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 4acb9ff..2424eb9 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"net/http"
 	"sort"
@@ -216,7 +217,19 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 			}
 		}
 		// Put the data back into the input
-		req.Body = ioutil.NopCloser(bytes.NewReader(data))
+		req.Body = nil
+		if req.GetBody != nil {
+			if req.Body, err = req.GetBody(); err != nil {
+				req.Body = nil
+			}
+		}
+		if req.Body == nil {
+			req.ContentLength = int64(len(data))
+			req.GetBody = func() (io.ReadCloser, error) {
+				return io.NopCloser(bytes.NewReader(data)), nil
+			}
+			req.Body, _ = req.GetBody() // no error return
+		}
 	}
 
 	if len(data) == 0 {
@@ -273,10 +286,12 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 
 	// Validate JSON with the schema
 	if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
+		schemaId := getSchemaIdentifier(contentType.Schema)
+		schemaId = prependSpaceIfNeeded(schemaId)
 		return &RequestError{
 			Input:       input,
 			RequestBody: requestBody,
-			Reason:      "doesn't match the schema",
+			Reason:      fmt.Sprintf("doesn't match schema%s", schemaId),
 			Err:         err,
 		}
 	}
@@ -292,8 +307,14 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 			}
 		}
 		// Put the data back into the input
-		req.Body = ioutil.NopCloser(bytes.NewReader(data))
+		if req.Body != nil {
+			req.Body.Close()
+		}
 		req.ContentLength = int64(len(data))
+		req.GetBody = func() (io.ReadCloser, error) {
+			return io.NopCloser(bytes.NewReader(data)), nil
+		}
+		req.Body, _ = req.GetBody() // no error return
 	}
 
 	return nil
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index abcbb4e..27bef82 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -8,6 +8,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"sort"
+	"strings"
 
 	"github.com/getkin/kin-openapi/openapi3"
 )
@@ -159,11 +160,36 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 
 	// Validate data with the schema.
 	if err := contentType.Schema.Value.VisitJSON(value, append(opts, openapi3.VisitAsResponse())...); err != nil {
+		schemaId := getSchemaIdentifier(contentType.Schema)
+		schemaId = prependSpaceIfNeeded(schemaId)
 		return &ResponseError{
 			Input:  input,
-			Reason: "response body doesn't match the schema",
+			Reason: fmt.Sprintf("response body doesn't match schema%s", schemaId),
 			Err:    err,
 		}
 	}
 	return nil
 }
+
+// getSchemaIdentifier gets something by which a schema could be identified.
+// A schema by itself doesn't have a true identity field. This function makes
+// a best effort to get a value that can fill that void.
+func getSchemaIdentifier(schema *openapi3.SchemaRef) string {
+	var id string
+
+	if schema != nil {
+		id = strings.TrimSpace(schema.Ref)
+	}
+	if id == "" && schema.Value != nil {
+		id = strings.TrimSpace(schema.Value.Title)
+	}
+
+	return id
+}
+
+func prependSpaceIfNeeded(value string) string {
+	if len(value) > 0 {
+		value = " " + value
+	}
+	return value
+}
diff --git a/routers/gorillamux/router.go b/routers/gorillamux/router.go
index 811ba7d..6977808 100644
--- a/routers/gorillamux/router.go
+++ b/routers/gorillamux/router.go
@@ -57,8 +57,6 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
 	muxRouter := mux.NewRouter().UseEncodedPath()
 	r := &Router{}
 	for _, path := range orderedPaths(doc.Paths) {
-		servers := servers
-
 		pathItem := doc.Paths[path]
 		if len(pathItem.Servers) > 0 {
 			if servers, err = makeServers(pathItem.Servers); err != nil {
@@ -140,19 +138,13 @@ func makeServers(in openapi3.Servers) ([]srv, error) {
 			if lhs := strings.TrimSuffix(serverURL, server.Variables[sVar].Default); lhs != "" {
 				varsUpdater = func(vars map[string]string) { vars[sVar] = lhs }
 			}
-			servers = append(servers, srv{
-				base:        server.Variables[sVar].Default,
-				server:      server,
-				varsUpdater: varsUpdater,
-			})
-			continue
-		}
+			svr, err := newSrv(serverURL, server, varsUpdater)
+			if err != nil {
+				return nil, err
+			}
 
-		var schemes []string
-		if strings.Contains(serverURL, "://") {
-			scheme0 := strings.Split(serverURL, "://")[0]
-			schemes = permutePart(scheme0, server)
-			serverURL = strings.Replace(serverURL, scheme0+"://", schemes[0]+"://", 1)
+			servers = append(servers, svr)
+			continue
 		}
 
 		// If a variable represents the port "http://domain.tld:{port}/bla"
@@ -172,21 +164,11 @@ func makeServers(in openapi3.Servers) ([]srv, error) {
 			}
 		}
 
-		u, err := url.Parse(bEncode(serverURL))
+		svr, err := newSrv(serverURL, server, varsUpdater)
 		if err != nil {
 			return nil, err
 		}
-		path := bDecode(u.EscapedPath())
-		if len(path) > 0 && path[len(path)-1] == '/' {
-			path = path[:len(path)-1]
-		}
-		servers = append(servers, srv{
-			host:        bDecode(u.Host), //u.Hostname()?
-			base:        path,
-			schemes:     schemes, // scheme: []string{scheme0}, TODO: https://github.com/gorilla/mux/issues/624
-			server:      server,
-			varsUpdater: varsUpdater,
-		})
+		servers = append(servers, svr)
 	}
 	if len(servers) == 0 {
 		servers = append(servers, srv{})
@@ -195,6 +177,32 @@ func makeServers(in openapi3.Servers) ([]srv, error) {
 	return servers, nil
 }
 
+func newSrv(serverURL string, server *openapi3.Server, varsUpdater varsf) (srv, error) {
+	var schemes []string
+	if strings.Contains(serverURL, "://") {
+		scheme0 := strings.Split(serverURL, "://")[0]
+		schemes = permutePart(scheme0, server)
+		serverURL = strings.Replace(serverURL, scheme0+"://", schemes[0]+"://", 1)
+	}
+
+	u, err := url.Parse(bEncode(serverURL))
+	if err != nil {
+		return srv{}, err
+	}
+	path := bDecode(u.EscapedPath())
+	if len(path) > 0 && path[len(path)-1] == '/' {
+		path = path[:len(path)-1]
+	}
+	svr := srv{
+		host:        bDecode(u.Host), //u.Hostname()?
+		base:        path,
+		schemes:     schemes, // scheme: []string{scheme0}, TODO: https://github.com/gorilla/mux/issues/624
+		server:      server,
+		varsUpdater: varsUpdater,
+	}
+	return svr, nil
+}
+
 func orderedPaths(paths map[string]*openapi3.PathItem) []string {
 	// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#pathsObject
 	// When matching URLs, concrete (non-templated) paths would be matched


Test patch:
diff --git a/openapi3/issue542_test.go b/openapi3/issue542_test.go
index 7e0cb88..4ba017a 100644
--- a/openapi3/issue542_test.go
+++ b/openapi3/issue542_test.go
@@ -10,6 +10,5 @@ func TestIssue542(t *testing.T) {
 	sl := NewLoader()
 
 	_, err := sl.LoadFromFile("testdata/issue542.yml")
-	require.Error(t, err)
-	require.Contains(t, err.Error(), CircularReferenceError)
+	require.NoError(t, err)
 }
diff --git a/openapi3/issue615_test.go b/openapi3/issue615_test.go
index ceb317a..e7bd01e 100644
--- a/openapi3/issue615_test.go
+++ b/openapi3/issue615_test.go
@@ -9,17 +9,11 @@ import (
 )
 
 func TestIssue615(t *testing.T) {
-	for {
+	{
 		loader := openapi3.NewLoader()
 		loader.IsExternalRefsAllowed = true
 		_, err := loader.LoadFromFile("testdata/recursiveRef/issue615.yml")
-		if err == nil {
-			continue
-		}
-		// Test currently reproduces the issue 615: failure to load a valid spec
-		// Upon issue resolution, this check should be changed to require.NoError
-		require.Error(t, err, openapi3.CircularReferenceError)
-		break
+		require.NoError(t, err)
 	}
 
 	var old int
diff --git a/openapi3/issue638_test.go b/openapi3/issue638_test.go
new file mode 100644
index 0000000..1db8a6f
--- /dev/null
+++ b/openapi3/issue638_test.go
@@ -0,0 +1,21 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue638(t *testing.T) {
+	for i := 0; i < 50; i++ {
+		loader := NewLoader()
+		loader.IsExternalRefsAllowed = true
+		// This path affects the occurrence of the issue #638.
+		// ../openapi3/testdata/issue638/test1.yaml : reproduce
+		// ./testdata/issue638/test1.yaml           : not reproduce
+		// testdata/issue638/test1.yaml             : reproduce
+		doc, err := loader.LoadFromFile("testdata/issue638/test1.yaml")
+		require.NoError(t, err)
+		require.Equal(t, "int", doc.Components.Schemas["test1d"].Value.Type)
+	}
+}
diff --git a/openapi3/issue697_test.go b/openapi3/issue697_test.go
new file mode 100644
index 0000000..c731758
--- /dev/null
+++ b/openapi3/issue697_test.go
@@ -0,0 +1,15 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue697(t *testing.T) {
+	loader := NewLoader()
+	doc, err := loader.LoadFromFile("testdata/issue697.yml")
+	require.NoError(t, err)
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+}
diff --git a/openapi3/security_scheme_test.go b/openapi3/security_scheme_test.go
index 5958c53..48ea046 100644
--- a/openapi3/security_scheme_test.go
+++ b/openapi3/security_scheme_test.go
@@ -30,7 +30,7 @@ func TestSecuritySchemaExample(t *testing.T) {
 	}
 }
 
-// from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#fixed-fields-23
+// from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-23
 var securitySchemeExamples = []securitySchemeExample{
 	{
 		title: "Basic Authentication Sample",
diff --git a/openapi3/testdata/issue638/test1.yaml b/openapi3/testdata/issue638/test1.yaml
new file mode 100644
index 0000000..f2ab555
--- /dev/null
+++ b/openapi3/testdata/issue638/test1.yaml
@@ -0,0 +1,15 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: reference test part 1
+  description: reference test part 1
+components:
+  schemas:
+    test1a:
+      $ref: "test2.yaml#/components/schemas/test2a"
+    test1b:
+      $ref: "#/components/schemas/test1c"
+    test1c:
+      type: int
+    test1d:
+      $ref: "test2.yaml#/components/schemas/test2b"
diff --git a/openapi3/testdata/issue638/test2.yaml b/openapi3/testdata/issue638/test2.yaml
new file mode 100644
index 0000000..d3ca464
--- /dev/null
+++ b/openapi3/testdata/issue638/test2.yaml
@@ -0,0 +1,13 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: reference test part 2
+  description: reference test part 2
+components:
+  schemas:
+    test2a:
+      type: number
+    test2b:
+      $ref: "test1.yaml#/components/schemas/test1b"
+    test1c:
+      type: string
diff --git a/openapi3/testdata/issue697.yml b/openapi3/testdata/issue697.yml
new file mode 100644
index 0000000..71a4b2a
--- /dev/null
+++ b/openapi3/testdata/issue697.yml
@@ -0,0 +1,14 @@
+openapi: 3.0.1
+components:
+  schemas:
+    API:
+      properties:
+        dateExample:
+          type: string
+          format: date
+          example: 2019-09-12
+info:
+  title: sample
+  version: version not set
+paths: {}
+
diff --git a/openapi3filter/issue625_test.go b/openapi3filter/issue625_test.go
index d9e5bae..5642a7e 100644
--- a/openapi3filter/issue625_test.go
+++ b/openapi3filter/issue625_test.go
@@ -72,7 +72,7 @@ paths:
 			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`,
+			errStr: `parameter "test" in query has an error: path 0: value 1.2: an invalid integer: invalid syntax`,
 		},
 		{
 			name: "success oneof object array",
diff --git a/openapi3filter/options_test.go b/openapi3filter/options_test.go
index a95b6bb..fd19329 100644
--- a/openapi3filter/options_test.go
+++ b/openapi3filter/options_test.go
@@ -78,5 +78,5 @@ paths:
 
 	fmt.Println(err.Error())
 
-	// Output: request body has an error: doesn't match the schema: field "Some field" must be an integer
+	// Output: request body has an error: doesn't match schema: field "Some field" must be an integer
 }
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 8bd62b1..709cdc9 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -175,7 +175,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "integer",
 					param: &openapi3.Parameter{Name: "param", In: "path", Schema: integerSchema},
 					path:  "/1",
-					want:  float64(1),
+					want:  int64(1),
 					found: true,
 				},
 				{
@@ -456,7 +456,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "integer",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: integerSchema},
 					query: "param=1",
-					want:  float64(1),
+					want:  int64(1),
 					found: true,
 				},
 				{
@@ -522,7 +522,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "anyofSchema integer",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: anyofSchema},
 					query: "param=1",
-					want:  float64(1),
+					want:  int64(1),
 					found: true,
 				},
 				{
@@ -548,7 +548,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:  "oneofSchema int",
 					param: &openapi3.Parameter{Name: "param", In: "query", Schema: oneofSchema},
 					query: "param=1122",
-					want:  float64(1122),
+					want:  int64(1122),
 					found: true,
 				},
 				{
@@ -724,7 +724,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "integer",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
 					header: "X-Param:1",
-					want:   float64(1),
+					want:   int64(1),
 					found:  true,
 				},
 				{
@@ -835,6 +835,13 @@ func TestDecodeParameter(t *testing.T) {
 					want:   map[string]interface{}{"id": "foo", "name": "bar"},
 					found:  true,
 				},
+				{
+					name:   "valid integer prop",
+					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: integerSchema},
+					header: "X-Param:88",
+					found:  true,
+					want:   int64(88),
+				},
 				{
 					name:   "invalid integer prop",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "header", Schema: objectOf("foo", integerSchema)},
@@ -893,7 +900,7 @@ func TestDecodeParameter(t *testing.T) {
 					name:   "integer",
 					param:  &openapi3.Parameter{Name: "X-Param", In: "cookie", Schema: integerSchema},
 					cookie: "X-Param:1",
-					want:   float64(1),
+					want:   int64(1),
 					found:  true,
 				},
 				{
@@ -1180,7 +1187,7 @@ func TestDecodeBody(t *testing.T) {
 				WithProperty("a", openapi3.NewStringSchema()).
 				WithProperty("b", openapi3.NewIntegerSchema()).
 				WithProperty("c", openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema())),
-			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}},
+			want: map[string]interface{}{"a": "a1", "b": int64(10), "c": []interface{}{"c1", "c2"}},
 		},
 		{
 			name: "urlencoded space delimited",
@@ -1193,7 +1200,7 @@ func TestDecodeBody(t *testing.T) {
 			encoding: map[string]*openapi3.Encoding{
 				"c": {Style: openapi3.SerializationSpaceDelimited, Explode: boolPtr(false)},
 			},
-			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}},
+			want: map[string]interface{}{"a": "a1", "b": int64(10), "c": []interface{}{"c1", "c2"}},
 		},
 		{
 			name: "urlencoded pipe delimited",
@@ -1206,7 +1213,7 @@ func TestDecodeBody(t *testing.T) {
 			encoding: map[string]*openapi3.Encoding{
 				"c": {Style: openapi3.SerializationPipeDelimited, Explode: boolPtr(false)},
 			},
-			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}},
+			want: map[string]interface{}{"a": "a1", "b": int64(10), "c": []interface{}{"c1", "c2"}},
 		},
 		{
 			name: "multipart",
diff --git a/openapi3filter/validate_request_test.go b/openapi3filter/validate_request_test.go
index 450ee59..8da550c 100644
--- a/openapi3filter/validate_request_test.go
+++ b/openapi3filter/validate_request_test.go
@@ -212,6 +212,12 @@ components:
 			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)
+
+			validationInput.Request.Body, err = validationInput.Request.GetBody()
+			assert.NoError(t, err, "unable to re-generate body by GetBody(): %v", err)
+			body2, err := io.ReadAll(validationInput.Request.Body)
+			assert.NoError(t, err, "unable to read request body: %v", err)
+			assert.Equal(t, body, body2, "body by GetBody() is not matched")
 		})
 	}
 }
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index 6fee135..b84d8bd 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -322,7 +322,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 			args: validationArgs{
 				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
 			},
-			wantErrReason:       "doesn't match the schema",
+			wantErrReason:       "doesn't match schema #/components/schemas/PetWithRequired",
 			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaValue:  "watdis",
 			wantErrSchemaPath:   "/status",
@@ -336,7 +336,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 			args: validationArgs{
 				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"name":"Bahama"}`)),
 			},
-			wantErrReason:       "doesn't match the schema",
+			wantErrReason:       "doesn't match schema #/components/schemas/PetWithRequired",
 			wantErrSchemaReason: `property "photoUrls" is missing`,
 			wantErrSchemaValue:  map[string]string{"name": "Bahama"},
 			wantErrSchemaPath:   "/photoUrls",
@@ -350,7 +350,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet",
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{}}`)),
 			},
-			wantErrReason:       "doesn't match the schema",
+			wantErrReason:       "doesn't match schema #/components/schemas/PetWithRequired",
 			wantErrSchemaReason: `property "name" is missing`,
 			wantErrSchemaValue:  map[string]string{},
 			wantErrSchemaPath:   "/category/name",
@@ -364,7 +364,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet",
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{"tags": [{}]}}`)),
 			},
-			wantErrReason:       "doesn't match the schema",
+			wantErrReason:       "doesn't match schema #/components/schemas/PetWithRequired",
 			wantErrSchemaReason: `property "name" is missing`,
 			wantErrSchemaValue:  map[string]string{},
 			wantErrSchemaPath:   "/category/tags/0/name",
@@ -378,7 +378,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet",
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`)),
 			},
-			wantErrReason:       "doesn't match the schema",
+			wantErrReason:       "doesn't match schema #/components/schemas/PetWithRequired",
 			wantErrSchemaReason: "field must be set to array or not be present",
 			wantErrSchemaPath:   "/photoUrls",
 			wantErrSchemaValue:  "string",
@@ -393,7 +393,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 			args: validationArgs{
 				r: newPetstoreRequest(t, http.MethodPost, "/pet2", bytes.NewBufferString(`{"name":"Bahama"}`)),
 			},
-			wantErrReason:             "doesn't match the schema",
+			wantErrReason:             "doesn't match schema",
 			wantErrSchemaPath:         "/",
 			wantErrSchemaValue:        map[string]string{"name": "Bahama"},
 			wantErrSchemaOriginReason: `property "photoUrls" is missing`,
diff --git a/routers/gorillamux/example_test.go b/routers/gorillamux/example_test.go
index 2ca3225..54058cd 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 schema pathref.openapi.yml#/components/schemas/TestSchema: field must be set to string or not be present
 	// Schema:
 	//   {
 	//     "type": "string"
diff --git a/routers/gorillamux/router_test.go b/routers/gorillamux/router_test.go
index 104056e..3e74400 100644
--- a/routers/gorillamux/router_test.go
+++ b/routers/gorillamux/router_test.go
@@ -6,6 +6,7 @@ import (
 	"sort"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/getkin/kin-openapi/openapi3"
@@ -249,7 +250,16 @@ func TestServerPath(t *testing.T) {
 			"http://example.com:{port}/path",
 			map[string]string{
 				"port": "8088",
-			})},
+			}),
+		newServerWithVariables(
+			"{server}",
+			map[string]string{
+				"server": "/",
+			}),
+		newServerWithVariables(
+			"/",
+			nil,
+		)},
 	})
 	require.NoError(t, err)
 }
@@ -325,6 +335,157 @@ func TestRelativeURL(t *testing.T) {
 	require.Equal(t, "/hello", route.Path)
 }
 
+func Test_makeServers(t *testing.T) {
+	type testStruct struct {
+		name    string
+		servers openapi3.Servers
+		want    []srv
+		wantErr bool
+		initFn  func(tt *testStruct)
+	}
+	tests := []testStruct{
+		{
+			name: "server is root path",
+			servers: openapi3.Servers{
+				newServerWithVariables("/", nil),
+			},
+			want: []srv{{
+				schemes:     nil,
+				host:        "",
+				base:        "",
+				server:      nil,
+				varsUpdater: nil,
+			}},
+			wantErr: false,
+			initFn: func(tt *testStruct) {
+				for i, server := range tt.servers {
+					tt.want[i].server = server
+				}
+			},
+		},
+		{
+			name: "server with single variable that evaluates to root path",
+			servers: openapi3.Servers{
+				newServerWithVariables("{server}", map[string]string{"server": "/"}),
+			},
+			want: []srv{{
+				schemes:     nil,
+				host:        "",
+				base:        "",
+				server:      nil,
+				varsUpdater: nil,
+			}},
+			wantErr: false,
+			initFn: func(tt *testStruct) {
+				for i, server := range tt.servers {
+					tt.want[i].server = server
+				}
+			},
+		},
+		{
+			name: "server is http://localhost:28002",
+			servers: openapi3.Servers{
+				newServerWithVariables("http://localhost:28002", nil),
+			},
+			want: []srv{{
+				schemes:     []string{"http"},
+				host:        "localhost:28002",
+				base:        "",
+				server:      nil,
+				varsUpdater: nil,
+			}},
+			wantErr: false,
+			initFn: func(tt *testStruct) {
+				for i, server := range tt.servers {
+					tt.want[i].server = server
+				}
+			},
+		},
+		{
+			name: "server with single variable that evaluates to http://localhost:28002",
+			servers: openapi3.Servers{
+				newServerWithVariables("{server}", map[string]string{"server": "http://localhost:28002"}),
+			},
+			want: []srv{{
+				schemes:     []string{"http"},
+				host:        "localhost:28002",
+				base:        "",
+				server:      nil,
+				varsUpdater: nil,
+			}},
+			wantErr: false,
+			initFn: func(tt *testStruct) {
+				for i, server := range tt.servers {
+					tt.want[i].server = server
+				}
+			},
+		},
+		{
+			name: "server with multiple variables that evaluates to http://localhost:28002",
+			servers: openapi3.Servers{
+				newServerWithVariables("{scheme}://{host}:{port}", map[string]string{"scheme": "http", "host": "localhost", "port": "28002"}),
+			},
+			want: []srv{{
+				schemes:     []string{"http"},
+				host:        "{host}:28002",
+				base:        "",
+				server:      nil,
+				varsUpdater: func(vars map[string]string) { vars["port"] = "28002" },
+			}},
+			wantErr: false,
+			initFn: func(tt *testStruct) {
+				for i, server := range tt.servers {
+					tt.want[i].server = server
+				}
+			},
+		},
+		{
+			name: "server with unparsable URL fails",
+			servers: openapi3.Servers{
+				newServerWithVariables("exam^ple.com:443", nil),
+			},
+			want:    nil,
+			wantErr: true,
+			initFn:  nil,
+		},
+		{
+			name: "server with single variable that evaluates to unparsable URL fails",
+			servers: openapi3.Servers{
+				newServerWithVariables("{server}", map[string]string{"server": "exam^ple.com:443"}),
+			},
+			want:    nil,
+			wantErr: true,
+			initFn:  nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.initFn != nil {
+				tt.initFn(&tt)
+			}
+			got, err := makeServers(tt.servers)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("makeServers() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			assert.Equal(t, len(tt.want), len(got), "expected and actual servers lengths are not equal")
+			for i := 0; i < len(tt.want); i++ {
+				// Unfortunately using assert.Equals or reflect.DeepEquals isn't
+				// an option because function pointers cannot be compared
+				assert.Equal(t, tt.want[i].schemes, got[i].schemes)
+				assert.Equal(t, tt.want[i].host, got[i].host)
+				assert.Equal(t, tt.want[i].host, got[i].host)
+				assert.Equal(t, tt.want[i].server, got[i].server)
+				if tt.want[i].varsUpdater == nil {
+					assert.Nil(t, got[i].varsUpdater, "expected and actual varsUpdater should point to same function")
+				} else {
+					assert.NotNil(t, got[i].varsUpdater, "expected and actual varsUpdater should point to same function")
+				}
+			}
+		})
+	}
+}
+
 func newServerWithVariables(url string, variables map[string]string) *openapi3.Server {
 	var serverVariables = map[string]*openapi3.ServerVariable{}
 
diff --git a/routers/legacy/validate_request_test.go b/routers/legacy/validate_request_test.go
index 5b9518c..9c15ed4 100644
--- a/routers/legacy/validate_request_test.go
+++ b/routers/legacy/validate_request_test.go
@@ -107,6 +107,6 @@ func Example() {
 		fmt.Println(err)
 	}
 	// Output:
-	// request body has an error: doesn't match the schema: input matches more than one oneOf schemas
+	// request body has an error: doesn't match schema: input matches more than one oneOf schemas
 
 }