Repository: getkin/kin-openapi
Pull request #708 (2022-12-15 12:37:34)

openapi3filter: use option to skip setting defaults on validation
this pull request solves https://github.com/getkin/kin-openapi/issues/707 issue


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 8094763..e1648fc 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -105,7 +105,7 @@ jobs:
     - if: runner.os == 'Linux'
       name: Missing specification object link to definition
       run: |
-        [[ 30 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]]
+        [[ 31 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]]
 
     - if: runner.os == 'Linux'
       name: Style around ExtensionProps embedding
diff --git a/README.md b/README.md
index c222649..3596289 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,11 @@ Be sure to check [OpenAPI Initiative](https://github.com/OAI)'s [great tooling l
     * Generates `*openapi3.Schema` values for Go types.
 
 # Some recipes
+## Validating an OpenAPI document
+```shell
+go run github.com/getkin/kin-openapi/cmd/validate@latest [--defaults] [--examples] [--ext] [--patterns] -- <local YAML or JSON file>
+```
+
 ## Loading OpenAPI document
 Use `openapi3.Loader`, which resolves all references:
 ```go
@@ -196,6 +201,11 @@ func arrayUniqueItemsChecker(items []interface{}) bool {
 
 ## Sub-v0 breaking API changes
 
+### v0.112.0
+* `(openapi3.ValidationOptions).ExamplesValidationDisabled` has been unexported.
+* `(openapi3.ValidationOptions).SchemaFormatValidationEnabled` has been unexported.
+* `(openapi3.ValidationOptions).SchemaPatternValidationDisabled` has been unexported.
+
 ### v0.111.0
 * Changed `func (*_) Validate(ctx context.Context) error` to `func (*_) Validate(ctx context.Context, opts ...ValidationOption) error`.
 * `openapi3.WithValidationOptions(ctx context.Context, opts *ValidationOptions) context.Context` prototype changed to `openapi3.WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context`.
diff --git a/cmd/validate/main.go b/cmd/validate/main.go
new file mode 100644
index 0000000..9759564
--- /dev/null
+++ b/cmd/validate/main.go
@@ -0,0 +1,102 @@
+package main
+
+import (
+	"flag"
+	"log"
+	"os"
+	"strings"
+
+	"github.com/invopop/yaml"
+
+	"github.com/getkin/kin-openapi/openapi2"
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+var (
+	defaultDefaults = true
+	defaults        = flag.Bool("defaults", defaultDefaults, "when false, disables schemas' default field validation")
+)
+
+var (
+	defaultExamples = true
+	examples        = flag.Bool("examples", defaultExamples, "when false, disables all example schema validation")
+)
+
+var (
+	defaultExt = false
+	ext        = flag.Bool("ext", defaultExt, "enables visiting other files")
+)
+
+var (
+	defaultPatterns = true
+	patterns        = flag.Bool("patterns", defaultPatterns, "when false, allows schema patterns unsupported by the Go regexp engine")
+)
+
+func main() {
+	flag.Parse()
+	filename := flag.Arg(0)
+	if len(flag.Args()) != 1 || filename == "" {
+		log.Fatalf("Usage: go run github.com/getkin/kin-openapi/cmd/validate@latest [--defaults] [--examples] [--ext] [--patterns] -- <local YAML or JSON file>\nGot: %+v\n", os.Args)
+	}
+
+	data, err := os.ReadFile(filename)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	var vd struct {
+		OpenAPI string `json:"openapi" yaml:"openapi"`
+		Swagger string `json:"swagger" yaml:"swagger"`
+	}
+	if err := yaml.Unmarshal(data, &vd); err != nil {
+		log.Fatal(err)
+	}
+
+	switch {
+	case vd.OpenAPI == "3" || strings.HasPrefix(vd.OpenAPI, "3."):
+		loader := openapi3.NewLoader()
+		loader.IsExternalRefsAllowed = *ext
+
+		doc, err := loader.LoadFromFile(filename)
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		var opts []openapi3.ValidationOption
+		if !*defaults {
+			opts = append(opts, openapi3.DisableSchemaDefaultsValidation())
+		}
+		if !*examples {
+			opts = append(opts, openapi3.DisableExamplesValidation())
+		}
+		if !*patterns {
+			opts = append(opts, openapi3.DisableSchemaPatternValidation())
+		}
+
+		if err = doc.Validate(loader.Context, opts...); err != nil {
+			log.Fatal(err)
+		}
+
+	case vd.Swagger == "2" || strings.HasPrefix(vd.Swagger, "2."):
+		if *defaults != defaultDefaults {
+			log.Fatal("Flag --defaults is only for OpenAPIv3")
+		}
+		if *examples != defaultExamples {
+			log.Fatal("Flag --examples is only for OpenAPIv3")
+		}
+		if *ext != defaultExt {
+			log.Fatal("Flag --ext is only for OpenAPIv3")
+		}
+		if *patterns != defaultPatterns {
+			log.Fatal("Flag --patterns is only for OpenAPIv3")
+		}
+
+		var doc openapi2.T
+		if err := yaml.Unmarshal(data, &doc); err != nil {
+			log.Fatal(err)
+		}
+
+	default:
+		log.Fatal("Missing or incorrect 'openapi' or 'swagger' field")
+	}
+}
diff --git a/jsoninfo/marshal.go b/jsoninfo/marshal.go
index 6e946d8..f2abf7c 100644
--- a/jsoninfo/marshal.go
+++ b/jsoninfo/marshal.go
@@ -23,7 +23,7 @@ type ObjectEncoder struct {
 
 func NewObjectEncoder() *ObjectEncoder {
 	return &ObjectEncoder{
-		result: make(map[string]json.RawMessage, 8),
+		result: make(map[string]json.RawMessage),
 	}
 }
 
diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go
index dcc5ddb..4927ade 100644
--- a/openapi2/openapi2.go
+++ b/openapi2/openapi2.go
@@ -40,15 +40,13 @@ func (doc *T) UnmarshalJSON(data []byte) error {
 }
 
 func (doc *T) AddOperation(path string, method string, operation *Operation) {
-	paths := doc.Paths
-	if paths == nil {
-		paths = make(map[string]*PathItem, 8)
-		doc.Paths = paths
+	if doc.Paths == nil {
+		doc.Paths = make(map[string]*PathItem)
 	}
-	pathItem := paths[path]
+	pathItem := doc.Paths[path]
 	if pathItem == nil {
 		pathItem = &PathItem{}
-		paths[path] = pathItem
+		doc.Paths[path] = pathItem
 	}
 	pathItem.SetOperation(method, operation)
 }
@@ -77,7 +75,7 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
 }
 
 func (pathItem *PathItem) Operations() map[string]*Operation {
-	operations := make(map[string]*Operation, 8)
+	operations := make(map[string]*Operation)
 	if v := pathItem.Delete; v != nil {
 		operations[http.MethodDelete] = v
 	}
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index 8c08205..989b685 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -1204,15 +1204,13 @@ func stripNonCustomExtensions(extensions map[string]interface{}) {
 }
 
 func addPathExtensions(doc2 *openapi2.T, path string, extensionProps openapi3.ExtensionProps) {
-	paths := doc2.Paths
-	if paths == nil {
-		paths = make(map[string]*openapi2.PathItem, 8)
-		doc2.Paths = paths
+	if doc2.Paths == nil {
+		doc2.Paths = make(map[string]*openapi2.PathItem)
 	}
-	pathItem := paths[path]
+	pathItem := doc2.Paths[path]
 	if pathItem == nil {
 		pathItem = &openapi2.PathItem{}
-		paths[path] = pathItem
+		doc2.Paths[path] = pathItem
 	}
 	pathItem.ExtensionProps = extensionProps
 }
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/content.go b/openapi3/content.go
index 8abd411..81b070e 100644
--- a/openapi3/content.go
+++ b/openapi3/content.go
@@ -10,7 +10,7 @@ import (
 type Content map[string]*MediaType
 
 func NewContent() Content {
-	return make(map[string]*MediaType, 4)
+	return make(map[string]*MediaType)
 }
 
 func NewContentWithSchema(schema *Schema, consumes []string) Content {
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/internalize_refs.go b/openapi3/internalize_refs.go
index 6ff6b85..dcec43b 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -193,14 +193,11 @@ func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver,
 		return false
 	}
 	name := refNameResolver(c.Ref)
-	if _, ok := doc.Components.Callbacks[name]; ok {
-		c.Ref = "#/components/callbacks/" + name
-	}
 	if doc.Components.Callbacks == nil {
 		doc.Components.Callbacks = make(Callbacks)
 	}
-	doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
 	c.Ref = "#/components/callbacks/" + name
+	doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
 	return true
 }
 
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 4cc83c0..ecc2ef2 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -281,12 +281,7 @@ func isSingleRefElement(ref string) bool {
 	return !strings.Contains(ref, "#")
 }
 
-func (loader *Loader) resolveComponent(
-	doc *T,
-	ref string,
-	path *url.URL,
-	resolved interface{},
-) (
+func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolved interface{}) (
 	componentDoc *T,
 	componentPath *url.URL,
 	err error,
@@ -928,11 +923,10 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 					}
 					id := unescapeRefString(rest)
 
-					definitions := doc.Components.Callbacks
-					if definitions == nil {
+					if doc.Components.Callbacks == nil {
 						return failedToResolveRefFragmentPart(ref, "callbacks")
 					}
-					resolved := definitions[id]
+					resolved := doc.Components.Callbacks[id]
 					if resolved == nil {
 						return failedToResolveRefFragmentPart(ref, id)
 					}
@@ -1022,15 +1016,13 @@ func (loader *Loader) resolvePathItemRef(doc *T, entrypoint string, pathItem *Pa
 			}
 			id := unescapeRefString(rest)
 
-			definitions := doc.Paths
-			if definitions == nil {
+			if doc.Paths == nil {
 				return failedToResolveRefFragmentPart(ref, "paths")
 			}
-			resolved := definitions[id]
+			resolved := doc.Paths[id]
 			if resolved == nil {
 				return failedToResolveRefFragmentPart(ref, id)
 			}
-
 			*pathItem = *resolved
 		}
 	}
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index 74c11b7..090be76 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:"-"`
 
@@ -90,7 +90,7 @@ func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOpti
 			return errors.New("example and examples are mutually exclusive")
 		}
 
-		if vo := getValidationOptions(ctx); vo.ExamplesValidationDisabled {
+		if vo := getValidationOptions(ctx); vo.examplesValidationDisabled {
 			return nil
 		}
 
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index 714f280..6622ef0 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:"-"`
 
@@ -36,15 +36,13 @@ func (doc *T) UnmarshalJSON(data []byte) error {
 }
 
 func (doc *T) AddOperation(path string, method string, operation *Operation) {
-	paths := doc.Paths
-	if paths == nil {
-		paths = make(Paths)
-		doc.Paths = paths
+	if doc.Paths == nil {
+		doc.Paths = make(Paths)
 	}
-	pathItem := paths[path]
+	pathItem := doc.Paths[path]
 	if pathItem == nil {
 		pathItem = &PathItem{}
-		paths[path] = pathItem
+		doc.Paths[path] = pathItem
 	}
 	pathItem.SetOperation(method, operation)
 }
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 9124d92..04e13b2 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:"-"`
 
@@ -323,7 +323,7 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti
 			return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name)
 		}
 
-		if vo := getValidationOptions(ctx); vo.ExamplesValidationDisabled {
+		if vo := getValidationOptions(ctx); vo.examplesValidationDisabled {
 			return nil
 		}
 		if example := parameter.Example; example != nil {
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 5cba0a8..5323dc1 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:"-"`
 
@@ -41,7 +41,7 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
 }
 
 func (pathItem *PathItem) Operations() map[string]*Operation {
-	operations := make(map[string]*Operation, 4)
+	operations := make(map[string]*Operation)
 	if v := pathItem.Connect; v != nil {
 		operations[http.MethodConnect] = v
 	}
diff --git a/openapi3/paths.go b/openapi3/paths.go
index 2af59f2..0986b05 100644
--- a/openapi3/paths.go
+++ b/openapi3/paths.go
@@ -29,8 +29,8 @@ func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error
 		}
 
 		if pathItem == nil {
-			paths[path] = &PathItem{}
-			pathItem = paths[path]
+			pathItem = &PathItem{}
+			paths[path] = pathItem
 		}
 
 		normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
@@ -109,6 +109,37 @@ func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error
 	return nil
 }
 
+// InMatchingOrder returns paths in the order they are matched against URLs.
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
+// When matching URLs, concrete (non-templated) paths would be matched
+// before their templated counterparts.
+func (paths Paths) InMatchingOrder() []string {
+	// NOTE: sorting by number of variables ASC then by descending lexicographical
+	// order seems to be a good heuristic.
+	if paths == nil {
+		return nil
+	}
+
+	vars := make(map[int][]string)
+	max := 0
+	for path := range paths {
+		count := strings.Count(path, "}")
+		vars[count] = append(vars[count], path)
+		if count > max {
+			max = count
+		}
+	}
+
+	ordered := make([]string, 0, len(paths))
+	for c := 0; c <= max; c++ {
+		if ps, ok := vars[c]; ok {
+			sort.Sort(sort.Reverse(sort.StringSlice(ps)))
+			ordered = append(ordered, ps...)
+		}
+	}
+	return ordered
+}
+
 // Find returns a path that matches the key.
 //
 // The method ignores differences in template variable names (except possible "*" suffix).
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..f0d9e1e 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:"-"`
 
@@ -112,7 +112,7 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation
 		return errors.New("content of the request body is required")
 	}
 
-	if vo := getValidationOptions(ctx); !vo.ExamplesValidationDisabled {
+	if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
 		vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false
 	}
 
diff --git a/openapi3/response.go b/openapi3/response.go
index d2f907d..324f77d 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:"-"`
 
@@ -119,7 +119,7 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption
 	if response.Description == nil {
 		return errors.New("a short description of the response is required")
 	}
-	if vo := getValidationOptions(ctx); !vo.ExamplesValidationDisabled {
+	if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
 		vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true
 	}
 
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 67cc7dc..d795fb5 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
@@ -626,7 +634,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		if v == nil {
 			return foundUnresolvedRef(item.Ref)
 		}
-		if err = v.validate(ctx, stack); err == nil {
+		if err = v.validate(ctx, stack); err != nil {
 			return
 		}
 	}
@@ -670,7 +678,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 			switch format {
 			case "float", "double":
 			default:
-				if validationOpts.SchemaFormatValidationEnabled {
+				if validationOpts.schemaFormatValidationEnabled {
 					return unsupportedFormat(format)
 				}
 			}
@@ -680,7 +688,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 			switch format {
 			case "int32", "int64":
 			default:
-				if validationOpts.SchemaFormatValidationEnabled {
+				if validationOpts.schemaFormatValidationEnabled {
 					return unsupportedFormat(format)
 				}
 			}
@@ -702,12 +710,12 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 			case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference":
 			default:
 				// Try to check for custom defined formats
-				if _, ok := SchemaStringFormats[format]; !ok && validationOpts.SchemaFormatValidationEnabled {
+				if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled {
 					return unsupportedFormat(format)
 				}
 			}
 		}
-		if schema.Pattern != "" && !validationOpts.SchemaPatternValidationDisabled {
+		if schema.Pattern != "" && !validationOpts.schemaPatternValidationDisabled {
 			if err = schema.compilePattern(); err != nil {
 				return err
 			}
@@ -763,13 +771,13 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		}
 	}
 
-	if v := schema.Default; v != nil {
+	if v := schema.Default; v != nil && !validationOpts.schemaDefaultsValidationDisabled {
 		if err := schema.VisitJSON(v); err != nil {
 			return fmt.Errorf("invalid default: %w", err)
 		}
 	}
 
-	if x := schema.Example; x != nil && !validationOpts.ExamplesValidationDisabled {
+	if x := schema.Example; x != nil && !validationOpts.examplesValidationDisabled {
 		if err := validateExampleValue(ctx, x, schema); err != nil {
 			return fmt.Errorf("invalid example: %w", err)
 		}
@@ -914,16 +922,35 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			if valuemap, okcheck := value.(map[string]interface{}); okcheck {
 				discriminatorVal, okcheck := valuemap[pn]
 				if !okcheck {
-					return errors.New("input does not contain the discriminator property")
+					return &SchemaError{
+						Schema:      schema,
+						SchemaField: "discriminator",
+						Reason:      fmt.Sprintf("input does not contain the discriminator property %q", pn),
+					}
 				}
 
 				discriminatorValString, okcheck := discriminatorVal.(string)
 				if !okcheck {
-					return errors.New("descriminator value is not a string")
+					valStr := "null"
+					if discriminatorVal != nil {
+						valStr = fmt.Sprintf("%v", discriminatorVal)
+					}
+
+					return &SchemaError{
+						Value:       discriminatorVal,
+						Schema:      schema,
+						SchemaField: "discriminator",
+						Reason:      fmt.Sprintf("value of discriminator property %q is not a string: %v", pn, valStr),
+					}
 				}
 
 				if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck {
-					return errors.New("input does not contain a valid discriminator value")
+					return &SchemaError{
+						Value:       discriminatorVal,
+						Schema:      schema,
+						SchemaField: "discriminator",
+						Reason:      fmt.Sprintf("discriminator property %q has invalid value: %q", pn, discriminatorVal),
+					}
 				}
 			}
 		}
@@ -1042,6 +1069,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 	return
 }
 
+// The value is not considered in visitJSONNull because according to the spec
+// "null is not supported as a type" unless `nullable` is also set to true
+// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types
+// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
 func (schema *Schema) visitJSONNull(settings *schemaValidationSettings) (err error) {
 	if schema.Nullable {
 		return
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/validation_options.go b/openapi3/validation_options.go
index a74364d..343b683 100644
--- a/openapi3/validation_options.go
+++ b/openapi3/validation_options.go
@@ -7,10 +7,11 @@ type ValidationOption func(options *ValidationOptions)
 
 // ValidationOptions provides configuration for validating OpenAPI documents.
 type ValidationOptions struct {
-	SchemaFormatValidationEnabled                    bool
-	SchemaPatternValidationDisabled                  bool
-	ExamplesValidationDisabled                       bool
 	examplesValidationAsReq, examplesValidationAsRes bool
+	examplesValidationDisabled                       bool
+	schemaDefaultsValidationDisabled                 bool
+	schemaFormatValidationEnabled                    bool
+	schemaPatternValidationDisabled                  bool
 }
 
 type validationOptionsKey struct{}
@@ -19,7 +20,7 @@ type validationOptionsKey struct{}
 // By default, schema format validation is disabled.
 func EnableSchemaFormatValidation() ValidationOption {
 	return func(options *ValidationOptions) {
-		options.SchemaFormatValidationEnabled = true
+		options.schemaFormatValidationEnabled = true
 	}
 }
 
@@ -27,7 +28,7 @@ func EnableSchemaFormatValidation() ValidationOption {
 // By default, schema format validation is disabled.
 func DisableSchemaFormatValidation() ValidationOption {
 	return func(options *ValidationOptions) {
-		options.SchemaFormatValidationEnabled = false
+		options.schemaFormatValidationEnabled = false
 	}
 }
 
@@ -35,14 +36,30 @@ func DisableSchemaFormatValidation() ValidationOption {
 // By default, schema pattern validation is enabled.
 func EnableSchemaPatternValidation() ValidationOption {
 	return func(options *ValidationOptions) {
-		options.SchemaPatternValidationDisabled = false
+		options.schemaPatternValidationDisabled = false
 	}
 }
 
 // DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine.
 func DisableSchemaPatternValidation() ValidationOption {
 	return func(options *ValidationOptions) {
-		options.SchemaPatternValidationDisabled = true
+		options.schemaPatternValidationDisabled = true
+	}
+}
+
+// EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation.
+// By default, schema default values are validated against their schema.
+func EnableSchemaDefaultsValidation() ValidationOption {
+	return func(options *ValidationOptions) {
+		options.schemaDefaultsValidationDisabled = false
+	}
+}
+
+// DisableSchemaDefaultsValidation disables schemas' default field validation.
+// By default, schema default values are validated against their schema.
+func DisableSchemaDefaultsValidation() ValidationOption {
+	return func(options *ValidationOptions) {
+		options.schemaDefaultsValidationDisabled = true
 	}
 }
 
@@ -50,7 +67,7 @@ func DisableSchemaPatternValidation() ValidationOption {
 // By default, all schema examples are validated.
 func EnableExamplesValidation() ValidationOption {
 	return func(options *ValidationOptions) {
-		options.ExamplesValidationDisabled = false
+		options.examplesValidationDisabled = false
 	}
 }
 
@@ -58,7 +75,7 @@ func EnableExamplesValidation() ValidationOption {
 // By default, all schema examples are validated.
 func DisableExamplesValidation() ValidationOption {
 	return func(options *ValidationOptions) {
-		options.ExamplesValidationDisabled = true
+		options.examplesValidationDisabled = true
 	}
 }
 
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 52c65af..4791b4a 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -339,7 +339,7 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 		}
 		_, found = vDecoder.values[param]
 	case *headerParamDecoder:
-		_, found = vDecoder.header[param]
+		_, found = vDecoder.header[http.CanonicalHeaderKey(param)]
 	case *cookieParamDecoder:
 		_, err := vDecoder.req.Cookie(param)
 		found = err != http.ErrNoCookie
@@ -888,14 +888,21 @@ func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, err
 
 // parsePrimitive returns a value that is created by parsing a source string to a primitive type
 // that is specified by a schema. The function returns nil when the source string is empty.
-// The function panics when a schema has a non primitive type.
+// The function panics when a schema has a non-primitive type.
 func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error) {
 	if raw == "" {
 		return nil, nil
 	}
 	switch schema.Value.Type {
 	case "integer":
-		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}
 		}
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 2424eb9..a61c57a 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -137,7 +137,7 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param
 	}
 
 	// Set default value if needed
-	if value == nil && schema != nil && schema.Default != nil {
+	if !options.SkipSettingDefaults && value == nil && schema != nil && schema.Default != nil {
 		value = schema.Default
 		req := input.Request
 		switch parameter.In {
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index 27bef82..c1be319 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -78,24 +78,10 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 		}
 	}
 	sort.Strings(headers)
-	for _, k := range headers {
-		s := response.Headers[k]
-		h := input.Header.Get(k)
-		if h == "" {
-			if s.Value.Required {
-				return &ResponseError{
-					Input:  input,
-					Reason: fmt.Sprintf("response header %q missing", k),
-				}
-			}
-			continue
-		}
-		if err := s.Value.Schema.Value.VisitJSON(h, opts...); err != nil {
-			return &ResponseError{
-				Input:  input,
-				Reason: fmt.Sprintf("response header %q doesn't match the schema", k),
-				Err:    err,
-			}
+	for _, headerName := range headers {
+		headerRef := response.Headers[headerName]
+		if err := validateResponseHeader(headerName, headerRef, input, opts); err != nil {
+			return err
 		}
 	}
 
@@ -171,6 +157,46 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 	return nil
 }
 
+func validateResponseHeader(headerName string, headerRef *openapi3.HeaderRef, input *ResponseValidationInput, opts []openapi3.SchemaValidationOption) error {
+	var err error
+	var decodedValue interface{}
+	var found bool
+	var sm *openapi3.SerializationMethod
+	dec := &headerParamDecoder{header: input.Header}
+
+	if sm, err = headerRef.Value.SerializationMethod(); err != nil {
+		return &ResponseError{
+			Input:  input,
+			Reason: fmt.Sprintf("unable to get header %q serialization method", headerName),
+			Err:    err,
+		}
+	}
+
+	if decodedValue, found, err = decodeValue(dec, headerName, sm, headerRef.Value.Schema, headerRef.Value.Required); err != nil {
+		return &ResponseError{
+			Input:  input,
+			Reason: fmt.Sprintf("unable to decode header %q value", headerName),
+			Err:    err,
+		}
+	}
+
+	if found {
+		if err = headerRef.Value.Schema.Value.VisitJSON(decodedValue, opts...); err != nil {
+			return &ResponseError{
+				Input:  input,
+				Reason: fmt.Sprintf("response header %q doesn't match schema", headerName),
+				Err:    err,
+			}
+		}
+	} else if headerRef.Value.Required {
+		return &ResponseError{
+			Input:  input,
+			Reason: fmt.Sprintf("response header %q missing", headerName),
+		}
+	}
+	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.
diff --git a/routers/gorillamux/router.go b/routers/gorillamux/router.go
index 811ba7d..bbf81ce 100644
--- a/routers/gorillamux/router.go
+++ b/routers/gorillamux/router.go
@@ -56,9 +56,7 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
 
 	muxRouter := mux.NewRouter().UseEncodedPath()
 	r := &Router{}
-	for _, path := range orderedPaths(doc.Paths) {
-		servers := servers
-
+	for _, path := range doc.Paths.InMatchingOrder() {
 		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,29 +177,30 @@ func makeServers(in openapi3.Servers) ([]srv, error) {
 	return servers, 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
-	// before their templated counterparts.
-	// NOTE: sorting by number of variables ASC then by descending lexicographical
-	// order seems to be a good heuristic.
-	vars := make(map[int][]string)
-	max := 0
-	for path := range paths {
-		count := strings.Count(path, "}")
-		vars[count] = append(vars[count], path)
-		if count > max {
-			max = count
-		}
+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)
 	}
-	ordered := make([]string, 0, len(paths))
-	for c := 0; c <= max; c++ {
-		if ps, ok := vars[c]; ok {
-			sort.Sort(sort.Reverse(sort.StringSlice(ps)))
-			ordered = append(ordered, ps...)
-		}
+
+	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 ordered
+	return svr, nil
 }
 
 // Magic strings that temporarily replace "{}" so net/url.Parse() works
diff --git a/routers/legacy/router.go b/routers/legacy/router.go
index 74e3873..911422b 100644
--- a/routers/legacy/router.go
+++ b/routers/legacy/router.go
@@ -124,7 +124,7 @@ func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]s
 				Reason: routers.ErrPathNotFound.Error(),
 			}
 		}
-		pathParams = make(map[string]string, 8)
+		pathParams = make(map[string]string)
 		paramNames, err := server.ParameterNames()
 		if err != nil {
 			return nil, nil, err


Test patch:
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/schema_oneOf_test.go b/openapi3/schema_oneOf_test.go
index d3e689d..1a8ea81 100644
--- a/openapi3/schema_oneOf_test.go
+++ b/openapi3/schema_oneOf_test.go
@@ -86,7 +86,7 @@ func TestVisitJSON_OneOf_MissingDiscriptorProperty(t *testing.T) {
 	err = s.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
 		"name": "snoopy",
 	})
-	require.EqualError(t, err, "input does not contain the discriminator property")
+	require.ErrorContains(t, err, "input does not contain the discriminator property \"$type\"\n")
 }
 
 func TestVisitJSON_OneOf_MissingDiscriptorValue(t *testing.T) {
@@ -96,7 +96,7 @@ func TestVisitJSON_OneOf_MissingDiscriptorValue(t *testing.T) {
 		"name":  "snoopy",
 		"$type": "snake",
 	})
-	require.EqualError(t, err, "input does not contain a valid discriminator value")
+	require.ErrorContains(t, err, "discriminator property \"$type\" has invalid value: \"snake\"")
 }
 
 func TestVisitJSON_OneOf_MissingField(t *testing.T) {
@@ -126,14 +126,14 @@ func TestVisitJSON_OneOf_BadDescriminatorType(t *testing.T) {
 		"scratches": true,
 		"$type":     1,
 	})
-	require.EqualError(t, err, "descriminator value is not a string")
+	require.ErrorContains(t, err, "value of discriminator property \"$type\" is not a string: 1")
 
 	err = s.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
 		"name":  "snoopy",
 		"barks": true,
 		"$type": nil,
 	})
-	require.EqualError(t, err, "descriminator value is not a string")
+	require.ErrorContains(t, err, "value of discriminator property \"$type\" is not a string: null")
 }
 
 func TestVisitJSON_OneOf_Path(t *testing.T) {
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/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/issue201_test.go b/openapi3filter/issue201_test.go
index 8b2b99d..7e2eaab 100644
--- a/openapi3filter/issue201_test.go
+++ b/openapi3filter/issue201_test.go
@@ -17,7 +17,7 @@ func TestIssue201(t *testing.T) {
 	loader := openapi3.NewLoader()
 	ctx := loader.Context
 	spec := `
-openapi: '3'
+openapi: '3.0.3'
 info:
   version: 1.0.0
   title: Sample API
@@ -37,20 +37,24 @@ paths:
               description: ''
               required: true
               schema:
+                type: string
                 pattern: '^blip$'
             x-blop:
               description: ''
               schema:
+                type: string
                 pattern: '^blop$'
             X-Blap:
               description: ''
               required: true
               schema:
+                type: string
                 pattern: '^blap$'
             X-Blup:
               description: ''
               required: true
               schema:
+                type: string
                 pattern: '^blup$'
 `[1:]
 
@@ -94,7 +98,7 @@ paths:
 		},
 
 		"invalid required header": {
-			err: `response header "X-Blup" doesn't match the schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
+			err: `response header "X-Blup" doesn't match 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
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/issue707_test.go b/openapi3filter/issue707_test.go
new file mode 100644
index 0000000..c0dbe64
--- /dev/null
+++ b/openapi3filter/issue707_test.go
@@ -0,0 +1,91 @@
+package openapi3filter
+
+import (
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestIssue707(t *testing.T) {
+	loader := openapi3.NewLoader()
+	ctx := loader.Context
+	spec := `
+    openapi: 3.0.0
+    info:
+     version: 1.0.0
+     title: Sample API
+    paths:
+     /items:
+      get:
+       description: Returns a list of stuff
+       parameters:
+        - description: parameter with a default value
+          explode: true
+          in: query
+          name: param-with-default
+          schema:
+           default: 124
+           type: integer
+           style: form
+          required: false
+       responses:
+        '200':
+           description: Successful response
+`[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
+		expectedQuery string
+	}{
+		{
+			name: "no defaults are added to requests parameters",
+			options: &Options{
+				SkipSettingDefaults: true,
+			},
+			expectedQuery: "",
+		},
+
+		{
+			name:          "defaults are added to requests",
+			expectedQuery: "param-with-default=124",
+		},
+	}
+
+	for _, testcase := range tests {
+		t.Run(testcase.name, func(t *testing.T) {
+			httpReq, err := http.NewRequest(http.MethodGet, "/items", strings.NewReader(""))
+			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)
+
+			require.NoError(t, err)
+			require.Equal(t, testcase.expectedQuery,
+				httpReq.URL.RawQuery, "default value must not be included")
+		})
+	}
+}
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_response_test.go b/openapi3filter/validate_response_test.go
new file mode 100644
index 0000000..5ce657b
--- /dev/null
+++ b/openapi3filter/validate_response_test.go
@@ -0,0 +1,215 @@
+package openapi3filter
+
+import (
+	"io"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func Test_validateResponseHeader(t *testing.T) {
+	type args struct {
+		headerName string
+		headerRef  *openapi3.HeaderRef
+	}
+	tests := []struct {
+		name            string
+		args            args
+		isHeaderPresent bool
+		headerVals      []string
+		wantErr         bool
+		wantErrMsg      string
+	}{
+		{
+			name: "test required string header with single string value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewStringSchema(), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"blab"},
+			wantErr:         false,
+		},
+		{
+			name: "test required string header with single, empty string value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewStringSchema(), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{""},
+			wantErr:         true,
+			wantErrMsg:      `response header "X-Blab" doesn't match schema: Value is not nullable`,
+		},
+		{
+			name: "test optional string header with single string value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewStringSchema(), false),
+			},
+			isHeaderPresent: false,
+			headerVals:      []string{"blab"},
+			wantErr:         false,
+		},
+		{
+			name: "test required, but missing string header",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewStringSchema(), true),
+			},
+			isHeaderPresent: false,
+			headerVals:      nil,
+			wantErr:         true,
+			wantErrMsg:      `response header "X-Blab" missing`,
+		},
+		{
+			name: "test integer header with single integer value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewIntegerSchema(), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"88"},
+			wantErr:         false,
+		},
+		{
+			name: "test integer header with single string value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewIntegerSchema(), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"blab"},
+			wantErr:         true,
+			wantErrMsg:      `unable to decode header "X-Blab" value: value blab: an invalid integer: invalid syntax`,
+		},
+		{
+			name: "test int64 header with single int64 value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewInt64Schema(), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"88"},
+			wantErr:         false,
+		},
+		{
+			name: "test int32 header with single int32 value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewInt32Schema(), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"88"},
+			wantErr:         false,
+		},
+		{
+			name: "test float64 header with single float64 value",
+			args: args{
+				headerName: "X-Blab",
+				headerRef:  newHeaderRef(openapi3.NewFloat64Schema(), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"88.87"},
+			wantErr:         false,
+		},
+		{
+			name: "test integer header with multiple csv integer values",
+			args: args{
+				headerName: "X-blab",
+				headerRef:  newHeaderRef(newArraySchema(openapi3.NewIntegerSchema()), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"87,88"},
+			wantErr:         false,
+		},
+		{
+			name: "test integer header with multiple integer values",
+			args: args{
+				headerName: "X-blab",
+				headerRef:  newHeaderRef(newArraySchema(openapi3.NewIntegerSchema()), true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"87", "88"},
+			wantErr:         false,
+		},
+		{
+			name: "test non-typed, nullable header with single string value",
+			args: args{
+				headerName: "X-blab",
+				headerRef:  newHeaderRef(&openapi3.Schema{Nullable: true}, true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"blab"},
+			wantErr:         false,
+		},
+		{
+			name: "test required non-typed, nullable header not present",
+			args: args{
+				headerName: "X-blab",
+				headerRef:  newHeaderRef(&openapi3.Schema{Nullable: true}, true),
+			},
+			isHeaderPresent: false,
+			headerVals:      []string{"blab"},
+			wantErr:         true,
+			wantErrMsg:      `response header "X-blab" missing`,
+		},
+		{
+			name: "test non-typed, non-nullable header with single string value",
+			args: args{
+				headerName: "X-blab",
+				headerRef:  newHeaderRef(&openapi3.Schema{Nullable: false}, true),
+			},
+			isHeaderPresent: true,
+			headerVals:      []string{"blab"},
+			wantErr:         true,
+			wantErrMsg:      `response header "X-blab" doesn't match schema: Value is not nullable`,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			input := newInputDefault()
+			opts := []openapi3.SchemaValidationOption(nil)
+			if tt.isHeaderPresent {
+				input.Header = map[string][]string{http.CanonicalHeaderKey(tt.args.headerName): tt.headerVals}
+			}
+
+			err := validateResponseHeader(tt.args.headerName, tt.args.headerRef, input, opts)
+			if tt.wantErr {
+				require.NotEmpty(t, tt.wantErrMsg, "wanted error message is not populated")
+				require.Error(t, err)
+				require.Contains(t, err.Error(), tt.wantErrMsg)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func newInputDefault() *ResponseValidationInput {
+	return &ResponseValidationInput{
+		RequestValidationInput: &RequestValidationInput{
+			Request:    nil,
+			PathParams: nil,
+			Route:      nil,
+		},
+		Status: 200,
+		Header: nil,
+		Body:   io.NopCloser(strings.NewReader(`{}`)),
+	}
+}
+
+func newHeaderRef(schema *openapi3.Schema, required bool) *openapi3.HeaderRef {
+	return &openapi3.HeaderRef{Value: &openapi3.Header{Parameter: openapi3.Parameter{Schema: &openapi3.SchemaRef{Value: schema}, Required: required}}}
+}
+
+func newArraySchema(schema *openapi3.Schema) *openapi3.Schema {
+	arraySchema := openapi3.NewArraySchema()
+	arraySchema.Items = openapi3.NewSchemaRef("", schema)
+
+	return arraySchema
+}
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{}