Repository: getkin/kin-openapi
Pull request #524 (2022-04-01 15:48:41)

Handle port number variable of servers given to gorillamux.NewRouter
fixes https://github.com/getkin/kin-openapi/issues/367
closes https://github.com/getkin/kin-openapi/pull/523

slessard: I don't think this change will work with the below server config. (I'm still a bit new to forking and trying to figure out the best way to get your branch into my fork to test it out. Sorry, I haven't yet figured that out.)

```
		{URL: "{server}", Variables: map[string]*openapi3.ServerVariable{
			"server": {Default: "/"},
		}},
```
slessard: OK, I figured out the forking/remote/branching thing. I pulled your issue367 branch, added the server config from my previous message and confirmed the unit tests fails for that server config.

```
=== RUN   TestRouter
    router_test.go:176: 
        	Error Trace:	router_test.go:176
        	Error:      	Received unexpected error:
        	            	mux: path must start with a slash, got "{server}/partial"
        	Test:       	TestRouter
--- FAIL: TestRouter (0.00s)
```


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 5cdecc9..70b6db6 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -66,6 +66,7 @@ jobs:
       env:
         GOARCH: '386'
     - run: go test ./...
+    - run: go test -count=2 ./...
     - run: go test -v -run TestRaceyPatternSchema -race ./...
       env:
         CGO_ENABLED: '1'
@@ -142,3 +143,15 @@ jobs:
           T
           Tag
           XML
+
+  check-goimports:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-go@v3
+      with:
+        go-version: '>=1.17.0'
+    - run: go install github.com/incu6us/goimports-reviser/v2@v2.5.1
+    - run: which goimports-reviser
+    - run: find . -type f -iname '*.go' ! -iname '*.pb.go' -exec goimports-reviser -file-path {} \;
+    - run: git --no-pager diff --exit-code
diff --git a/go.mod b/go.mod
index df32d6a..50aba58 100644
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,10 @@ module github.com/getkin/kin-openapi
 go 1.16
 
 require (
-	github.com/ghodss/yaml v1.0.0
 	github.com/go-openapi/jsonpointer v0.19.5
 	github.com/gorilla/mux v1.8.0
+	github.com/invopop/yaml v0.1.0
 	github.com/stretchr/testify v1.5.1
-	gopkg.in/yaml.v2 v2.3.0
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1
 )
diff --git a/go.sum b/go.sum
index 2b289d7..a123aaf 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,14 @@
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
+github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -27,5 +27,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go
index 167bc57..c504288 100644
--- a/openapi2/openapi2.go
+++ b/openapi2/openapi2.go
@@ -21,18 +21,20 @@ type T struct {
 	Host                string                         `json:"host,omitempty" yaml:"host,omitempty"`
 	BasePath            string                         `json:"basePath,omitempty" yaml:"basePath,omitempty"`
 	Paths               map[string]*PathItem           `json:"paths,omitempty" yaml:"paths,omitempty"`
-	Definitions         map[string]*openapi3.SchemaRef `json:"definitions,omitempty,noref" yaml:"definitions,omitempty,noref"`
-	Parameters          map[string]*Parameter          `json:"parameters,omitempty,noref" yaml:"parameters,omitempty,noref"`
-	Responses           map[string]*Response           `json:"responses,omitempty,noref" yaml:"responses,omitempty,noref"`
+	Definitions         map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
+	Parameters          map[string]*Parameter          `json:"parameters,omitempty" yaml:"parameters,omitempty"`
+	Responses           map[string]*Response           `json:"responses,omitempty" yaml:"responses,omitempty"`
 	SecurityDefinitions map[string]*SecurityScheme     `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
 	Security            SecurityRequirements           `json:"security,omitempty" yaml:"security,omitempty"`
 	Tags                openapi3.Tags                  `json:"tags,omitempty" yaml:"tags,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of T.
 func (doc *T) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(doc)
 }
 
+// UnmarshalJSON sets T to a copy of data.
 func (doc *T) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, doc)
 }
@@ -64,10 +66,12 @@ type PathItem struct {
 	Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of PathItem.
 func (pathItem *PathItem) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(pathItem)
 }
 
+// UnmarshalJSON sets PathItem to a copy of data.
 func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, pathItem)
 }
@@ -155,10 +159,12 @@ type Operation struct {
 	Security     *SecurityRequirements  `json:"security,omitempty" yaml:"security,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of Operation.
 func (operation *Operation) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(operation)
 }
 
+// UnmarshalJSON sets Operation to a copy of data.
 func (operation *Operation) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, operation)
 }
@@ -207,10 +213,12 @@ type Parameter struct {
 	Default          interface{}         `json:"default,omitempty" yaml:"default,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of Parameter.
 func (parameter *Parameter) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(parameter)
 }
 
+// UnmarshalJSON sets Parameter to a copy of data.
 func (parameter *Parameter) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, parameter)
 }
@@ -224,10 +232,12 @@ type Response struct {
 	Examples    map[string]interface{} `json:"examples,omitempty" yaml:"examples,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of Response.
 func (response *Response) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(response)
 }
 
+// UnmarshalJSON sets Response to a copy of data.
 func (response *Response) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, response)
 }
@@ -236,10 +246,12 @@ type Header struct {
 	Parameter
 }
 
+// MarshalJSON returns the JSON encoding of Header.
 func (header *Header) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(header)
 }
 
+// UnmarshalJSON sets Header to a copy of data.
 func (header *Header) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, header)
 }
@@ -260,10 +272,12 @@ type SecurityScheme struct {
 	Tags             openapi3.Tags     `json:"tags,omitempty" yaml:"tags,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of SecurityScheme.
 func (securityScheme *SecurityScheme) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(securityScheme)
 }
 
+// UnmarshalJSON sets SecurityScheme to a copy of data.
 func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, securityScheme)
 }
diff --git a/openapi3/callback.go b/openapi3/callback.go
index 5f883c1..718f47c 100644
--- a/openapi3/callback.go
+++ b/openapi3/callback.go
@@ -11,6 +11,7 @@ type Callbacks map[string]*CallbackRef
 
 var _ jsonpointer.JSONPointable = (*Callbacks)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (c Callbacks) JSONLookup(token string) (interface{}, error) {
 	ref, ok := c[token]
 	if ref == nil || !ok {
@@ -27,8 +28,9 @@ func (c Callbacks) JSONLookup(token string) (interface{}, error) {
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callbackObject
 type Callback map[string]*PathItem
 
-func (value Callback) Validate(ctx context.Context) error {
-	for _, v := range value {
+// Validate returns an error if Callback does not comply with the OpenAPI spec.
+func (callback Callback) Validate(ctx context.Context) error {
+	for _, v := range callback {
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
diff --git a/openapi3/components.go b/openapi3/components.go
index 42af634..2f19943 100644
--- a/openapi3/components.go
+++ b/openapi3/components.go
@@ -28,14 +28,17 @@ func NewComponents() Components {
 	return Components{}
 }
 
+// MarshalJSON returns the JSON encoding of Components.
 func (components *Components) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(components)
 }
 
+// UnmarshalJSON sets Components to a copy of data.
 func (components *Components) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, components)
 }
 
+// Validate returns an error if Components does not comply with the OpenAPI spec.
 func (components *Components) Validate(ctx context.Context) (err error) {
 	for k, v := range components.Schemas {
 		if err = ValidateIdentifier(k); err != nil {
@@ -91,6 +94,33 @@ func (components *Components) Validate(ctx context.Context) (err error) {
 		}
 	}
 
+	for k, v := range components.Examples {
+		if err = ValidateIdentifier(k); err != nil {
+			return
+		}
+		if err = v.Validate(ctx); err != nil {
+			return
+		}
+	}
+
+	for k, v := range components.Links {
+		if err = ValidateIdentifier(k); err != nil {
+			return
+		}
+		if err = v.Validate(ctx); err != nil {
+			return
+		}
+	}
+
+	for k, v := range components.Callbacks {
+		if err = ValidateIdentifier(k); err != nil {
+			return
+		}
+		if err = v.Validate(ctx); err != nil {
+			return
+		}
+	}
+
 	return
 }
 
diff --git a/openapi3/content.go b/openapi3/content.go
index 5edb7d3..10e3e60 100644
--- a/openapi3/content.go
+++ b/openapi3/content.go
@@ -104,9 +104,9 @@ func (content Content) Get(mime string) *MediaType {
 	return content["*/*"]
 }
 
-func (value Content) Validate(ctx context.Context) error {
-	for _, v := range value {
-		// Validate MediaType
+// Validate returns an error if Content does not comply with the OpenAPI spec.
+func (content Content) Validate(ctx context.Context) error {
+	for _, v := range content {
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go
index 5e181a2..4cc4df9 100644
--- a/openapi3/discriminator.go
+++ b/openapi3/discriminator.go
@@ -15,14 +15,17 @@ type Discriminator struct {
 	Mapping      map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
 }
 
-func (value *Discriminator) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(value)
+// MarshalJSON returns the JSON encoding of Discriminator.
+func (discriminator *Discriminator) MarshalJSON() ([]byte, error) {
+	return jsoninfo.MarshalStrictStruct(discriminator)
 }
 
-func (value *Discriminator) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, value)
+// UnmarshalJSON sets Discriminator to a copy of data.
+func (discriminator *Discriminator) UnmarshalJSON(data []byte) error {
+	return jsoninfo.UnmarshalStrictStruct(data, discriminator)
 }
 
-func (value *Discriminator) Validate(ctx context.Context) error {
+// Validate returns an error if Discriminator does not comply with the OpenAPI spec.
+func (discriminator *Discriminator) Validate(ctx context.Context) error {
 	return nil
 }
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index b0dab7b..e6453ec 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -39,10 +39,12 @@ func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding {
 	return encoding
 }
 
+// MarshalJSON returns the JSON encoding of Encoding.
 func (encoding *Encoding) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(encoding)
 }
 
+// UnmarshalJSON sets Encoding to a copy of data.
 func (encoding *Encoding) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, encoding)
 }
@@ -62,11 +64,12 @@ func (encoding *Encoding) SerializationMethod() *SerializationMethod {
 	return sm
 }
 
-func (value *Encoding) Validate(ctx context.Context) error {
-	if value == nil {
+// Validate returns an error if Encoding does not comply with the OpenAPI spec.
+func (encoding *Encoding) Validate(ctx context.Context) error {
+	if encoding == nil {
 		return nil
 	}
-	for k, v := range value.Headers {
+	for k, v := range encoding.Headers {
 		if err := ValidateIdentifier(k); err != nil {
 			return nil
 		}
@@ -76,7 +79,7 @@ func (value *Encoding) Validate(ctx context.Context) error {
 	}
 
 	// Validate a media types's serialization method.
-	sm := value.SerializationMethod()
+	sm := encoding.SerializationMethod()
 	switch {
 	case sm.Style == SerializationForm && sm.Explode,
 		sm.Style == SerializationForm && !sm.Explode,
diff --git a/openapi3/example.go b/openapi3/example.go
index 19cceb4..ee40d9e 100644
--- a/openapi3/example.go
+++ b/openapi3/example.go
@@ -4,14 +4,16 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type Examples map[string]*ExampleRef
 
 var _ jsonpointer.JSONPointable = (*Examples)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (e Examples) JSONLookup(token string) (interface{}, error) {
 	ref, ok := e[token]
 	if ref == nil || !ok {
@@ -41,14 +43,17 @@ func NewExample(value interface{}) *Example {
 	}
 }
 
+// MarshalJSON returns the JSON encoding of Example.
 func (example *Example) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(example)
 }
 
+// UnmarshalJSON sets Example to a copy of data.
 func (example *Example) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, example)
 }
 
-func (value *Example) Validate(ctx context.Context) error {
+// Validate returns an error if Example does not comply with the OpenAPI spec.
+func (example *Example) Validate(ctx context.Context) error {
 	return nil // TODO
 }
diff --git a/openapi3/external_docs.go b/openapi3/external_docs.go
index bb9dd5a..17a38ee 100644
--- a/openapi3/external_docs.go
+++ b/openapi3/external_docs.go
@@ -18,14 +18,17 @@ type ExternalDocs struct {
 	URL         string `json:"url,omitempty" yaml:"url,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of ExternalDocs.
 func (e *ExternalDocs) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(e)
 }
 
+// UnmarshalJSON sets ExternalDocs to a copy of data.
 func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, e)
 }
 
+// Validate returns an error if ExternalDocs does not comply with the OpenAPI spec.
 func (e *ExternalDocs) Validate(ctx context.Context) error {
 	if e.URL == "" {
 		return errors.New("url is required")
diff --git a/openapi3/header.go b/openapi3/header.go
index 9adb5ac..75e5dd1 100644
--- a/openapi3/header.go
+++ b/openapi3/header.go
@@ -5,14 +5,16 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type Headers map[string]*HeaderRef
 
 var _ jsonpointer.JSONPointable = (*Headers)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (h Headers) JSONLookup(token string) (interface{}, error) {
 	ref, ok := h[token]
 	if ref == nil || !ok {
@@ -33,33 +35,35 @@ type Header struct {
 
 var _ jsonpointer.JSONPointable = (*Header)(nil)
 
-func (value *Header) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, value)
+// UnmarshalJSON sets Header to a copy of data.
+func (header *Header) UnmarshalJSON(data []byte) error {
+	return jsoninfo.UnmarshalStrictStruct(data, header)
 }
 
 // SerializationMethod returns a header's serialization method.
-func (value *Header) SerializationMethod() (*SerializationMethod, error) {
-	style := value.Style
+func (header *Header) SerializationMethod() (*SerializationMethod, error) {
+	style := header.Style
 	if style == "" {
 		style = SerializationSimple
 	}
 	explode := false
-	if value.Explode != nil {
-		explode = *value.Explode
+	if header.Explode != nil {
+		explode = *header.Explode
 	}
 	return &SerializationMethod{Style: style, Explode: explode}, nil
 }
 
-func (value *Header) Validate(ctx context.Context) error {
-	if value.Name != "" {
+// Validate returns an error if Header does not comply with the OpenAPI spec.
+func (header *Header) Validate(ctx context.Context) error {
+	if header.Name != "" {
 		return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map")
 	}
-	if value.In != "" {
+	if header.In != "" {
 		return errors.New("header 'in' MUST NOT be specified, it is implicitly in header")
 	}
 
 	// Validate a parameter's serialization method.
-	sm, err := value.SerializationMethod()
+	sm, err := header.SerializationMethod()
 	if err != nil {
 		return err
 	}
@@ -70,17 +74,17 @@ func (value *Header) Validate(ctx context.Context) error {
 		return fmt.Errorf("header schema is invalid: %v", e)
 	}
 
-	if (value.Schema == nil) == (value.Content == nil) {
-		e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", value)
+	if (header.Schema == nil) == (header.Content == nil) {
+		e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", header)
 		return fmt.Errorf("header schema is invalid: %v", e)
 	}
-	if schema := value.Schema; schema != nil {
+	if schema := header.Schema; schema != nil {
 		if err := schema.Validate(ctx); err != nil {
 			return fmt.Errorf("header schema is invalid: %v", err)
 		}
 	}
 
-	if content := value.Content; content != nil {
+	if content := header.Content; content != nil {
 		if err := content.Validate(ctx); err != nil {
 			return fmt.Errorf("header content is invalid: %v", err)
 		}
@@ -88,41 +92,42 @@ func (value *Header) Validate(ctx context.Context) error {
 	return nil
 }
 
-func (value Header) JSONLookup(token string) (interface{}, error) {
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
+func (header Header) JSONLookup(token string) (interface{}, error) {
 	switch token {
 	case "schema":
-		if value.Schema != nil {
-			if value.Schema.Ref != "" {
-				return &Ref{Ref: value.Schema.Ref}, nil
+		if header.Schema != nil {
+			if header.Schema.Ref != "" {
+				return &Ref{Ref: header.Schema.Ref}, nil
 			}
-			return value.Schema.Value, nil
+			return header.Schema.Value, nil
 		}
 	case "name":
-		return value.Name, nil
+		return header.Name, nil
 	case "in":
-		return value.In, nil
+		return header.In, nil
 	case "description":
-		return value.Description, nil
+		return header.Description, nil
 	case "style":
-		return value.Style, nil
+		return header.Style, nil
 	case "explode":
-		return value.Explode, nil
+		return header.Explode, nil
 	case "allowEmptyValue":
-		return value.AllowEmptyValue, nil
+		return header.AllowEmptyValue, nil
 	case "allowReserved":
-		return value.AllowReserved, nil
+		return header.AllowReserved, nil
 	case "deprecated":
-		return value.Deprecated, nil
+		return header.Deprecated, nil
 	case "required":
-		return value.Required, nil
+		return header.Required, nil
 	case "example":
-		return value.Example, nil
+		return header.Example, nil
 	case "examples":
-		return value.Examples, nil
+		return header.Examples, nil
 	case "content":
-		return value.Content, nil
+		return header.Content, nil
 	}
 
-	v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token)
+	v, _, err := jsonpointer.GetForToken(header.ExtensionProps, token)
 	return v, err
 }
diff --git a/openapi3/info.go b/openapi3/info.go
index 6b41589..e60f888 100644
--- a/openapi3/info.go
+++ b/openapi3/info.go
@@ -20,32 +20,35 @@ type Info struct {
 	Version        string   `json:"version" yaml:"version"` // Required
 }
 
-func (value *Info) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(value)
+// MarshalJSON returns the JSON encoding of Info.
+func (info *Info) MarshalJSON() ([]byte, error) {
+	return jsoninfo.MarshalStrictStruct(info)
 }
 
-func (value *Info) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, value)
+// UnmarshalJSON sets Info to a copy of data.
+func (info *Info) UnmarshalJSON(data []byte) error {
+	return jsoninfo.UnmarshalStrictStruct(data, info)
 }
 
-func (value *Info) Validate(ctx context.Context) error {
-	if contact := value.Contact; contact != nil {
+// Validate returns an error if Info does not comply with the OpenAPI spec.
+func (info *Info) Validate(ctx context.Context) error {
+	if contact := info.Contact; contact != nil {
 		if err := contact.Validate(ctx); err != nil {
 			return err
 		}
 	}
 
-	if license := value.License; license != nil {
+	if license := info.License; license != nil {
 		if err := license.Validate(ctx); err != nil {
 			return err
 		}
 	}
 
-	if value.Version == "" {
+	if info.Version == "" {
 		return errors.New("value of version must be a non-empty string")
 	}
 
-	if value.Title == "" {
+	if info.Title == "" {
 		return errors.New("value of title must be a non-empty string")
 	}
 
@@ -62,15 +65,18 @@ type Contact struct {
 	Email string `json:"email,omitempty" yaml:"email,omitempty"`
 }
 
-func (value *Contact) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(value)
+// MarshalJSON returns the JSON encoding of Contact.
+func (contact *Contact) MarshalJSON() ([]byte, error) {
+	return jsoninfo.MarshalStrictStruct(contact)
 }
 
-func (value *Contact) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, value)
+// UnmarshalJSON sets Contact to a copy of data.
+func (contact *Contact) UnmarshalJSON(data []byte) error {
+	return jsoninfo.UnmarshalStrictStruct(data, contact)
 }
 
-func (value *Contact) Validate(ctx context.Context) error {
+// Validate returns an error if Contact does not comply with the OpenAPI spec.
+func (contact *Contact) Validate(ctx context.Context) error {
 	return nil
 }
 
@@ -83,16 +89,19 @@ type License struct {
 	URL  string `json:"url,omitempty" yaml:"url,omitempty"`
 }
 
-func (value *License) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(value)
+// MarshalJSON returns the JSON encoding of License.
+func (license *License) MarshalJSON() ([]byte, error) {
+	return jsoninfo.MarshalStrictStruct(license)
 }
 
-func (value *License) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, value)
+// UnmarshalJSON sets License to a copy of data.
+func (license *License) UnmarshalJSON(data []byte) error {
+	return jsoninfo.UnmarshalStrictStruct(data, license)
 }
 
-func (value *License) Validate(ctx context.Context) error {
-	if value.Name == "" {
+// Validate returns an error if License does not comply with the OpenAPI spec.
+func (license *License) Validate(ctx context.Context) error {
+	if license.Name == "" {
 		return errors.New("value of license name must be a non-empty string")
 	}
 	return nil
diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go
index 3a993bf..3a6cabb 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -286,6 +286,10 @@ func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameReso
 		// inline full operations
 		ops.Ref = ""
 
+		for _, param := range ops.Parameters {
+			doc.addParameterToSpec(param, refNameResolver)
+		}
+
 		for _, op := range ops.Operations() {
 			doc.addRequestBodyToSpec(op.RequestBody, refNameResolver)
 			if op.RequestBody != nil && op.RequestBody.Value != nil {
diff --git a/openapi3/link.go b/openapi3/link.go
index 19a725a..7f0c49d 100644
--- a/openapi3/link.go
+++ b/openapi3/link.go
@@ -5,14 +5,16 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type Links map[string]*LinkRef
 
-func (l Links) JSONLookup(token string) (interface{}, error) {
-	ref, ok := l[token]
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
+func (links Links) JSONLookup(token string) (interface{}, error) {
+	ref, ok := links[token]
 	if ok == false {
 		return nil, fmt.Errorf("object has no field %q", token)
 	}
@@ -38,20 +40,23 @@ type Link struct {
 	RequestBody  interface{}            `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
 }
 
-func (value *Link) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(value)
+// MarshalJSON returns the JSON encoding of Link.
+func (link *Link) MarshalJSON() ([]byte, error) {
+	return jsoninfo.MarshalStrictStruct(link)
 }
 
-func (value *Link) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, value)
+// UnmarshalJSON sets Link to a copy of data.
+func (link *Link) UnmarshalJSON(data []byte) error {
+	return jsoninfo.UnmarshalStrictStruct(data, link)
 }
 
-func (value *Link) Validate(ctx context.Context) error {
-	if value.OperationID == "" && value.OperationRef == "" {
+// Validate returns an error if Link does not comply with the OpenAPI spec.
+func (link *Link) Validate(ctx context.Context) error {
+	if link.OperationID == "" && link.OperationRef == "" {
 		return errors.New("missing operationId or operationRef on link")
 	}
-	if value.OperationID != "" && value.OperationRef != "" {
-		return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", value.OperationID, value.OperationRef)
+	if link.OperationID != "" && link.OperationRef != "" {
+		return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", link.OperationID, link.OperationRef)
 	}
 	return nil
 }
diff --git a/openapi3/loader.go b/openapi3/loader.go
index 8af733c..e2f131e 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -12,7 +12,7 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/ghodss/yaml"
+	"github.com/invopop/yaml"
 )
 
 func foundUnresolvedRef(ref string) error {
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index 5c001ca..fc95244 100644
--- a/openapi3/media_type.go
+++ b/openapi3/media_type.go
@@ -3,8 +3,9 @@ package openapi3
 import (
 	"context"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // MediaType is specified by OpenAPI/Swagger 3.0 standard.
@@ -60,19 +61,22 @@ func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType
 	return mediaType
 }
 
+// MarshalJSON returns the JSON encoding of MediaType.
 func (mediaType *MediaType) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(mediaType)
 }
 
+// UnmarshalJSON sets MediaType to a copy of data.
 func (mediaType *MediaType) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, mediaType)
 }
 
-func (value *MediaType) Validate(ctx context.Context) error {
-	if value == nil {
+// Validate returns an error if MediaType does not comply with the OpenAPI spec.
+func (mediaType *MediaType) Validate(ctx context.Context) error {
+	if mediaType == nil {
 		return nil
 	}
-	if schema := value.Schema; schema != nil {
+	if schema := mediaType.Schema; schema != nil {
 		if err := schema.Validate(ctx); err != nil {
 			return err
 		}
@@ -80,6 +84,7 @@ func (value *MediaType) Validate(ctx context.Context) error {
 	return nil
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (mediaType MediaType) JSONLookup(token string) (interface{}, error) {
 	switch token {
 	case "schema":
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index d376812..c0188c2 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -23,10 +23,12 @@ type T struct {
 	ExternalDocs *ExternalDocs        `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of T.
 func (doc *T) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(doc)
 }
 
+// UnmarshalJSON sets T to a copy of data.
 func (doc *T) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, doc)
 }
@@ -49,8 +51,9 @@ func (doc *T) AddServer(server *Server) {
 	doc.Servers = append(doc.Servers, server)
 }
 
-func (value *T) Validate(ctx context.Context) error {
-	if value.OpenAPI == "" {
+// Validate returns an error if T does not comply with the OpenAPI spec.
+func (doc *T) Validate(ctx context.Context) error {
+	if doc.OpenAPI == "" {
 		return errors.New("value of openapi must be a non-empty string")
 	}
 
@@ -58,14 +61,14 @@ func (value *T) Validate(ctx context.Context) error {
 
 	{
 		wrap := func(e error) error { return fmt.Errorf("invalid components: %v", e) }
-		if err := value.Components.Validate(ctx); err != nil {
+		if err := doc.Components.Validate(ctx); err != nil {
 			return wrap(err)
 		}
 	}
 
 	{
 		wrap := func(e error) error { return fmt.Errorf("invalid info: %v", e) }
-		if v := value.Info; v != nil {
+		if v := doc.Info; v != nil {
 			if err := v.Validate(ctx); err != nil {
 				return wrap(err)
 			}
@@ -76,7 +79,7 @@ func (value *T) Validate(ctx context.Context) error {
 
 	{
 		wrap := func(e error) error { return fmt.Errorf("invalid paths: %v", e) }
-		if v := value.Paths; v != nil {
+		if v := doc.Paths; v != nil {
 			if err := v.Validate(ctx); err != nil {
 				return wrap(err)
 			}
@@ -87,7 +90,7 @@ func (value *T) Validate(ctx context.Context) error {
 
 	{
 		wrap := func(e error) error { return fmt.Errorf("invalid security: %v", e) }
-		if v := value.Security; v != nil {
+		if v := doc.Security; v != nil {
 			if err := v.Validate(ctx); err != nil {
 				return wrap(err)
 			}
@@ -96,7 +99,7 @@ func (value *T) Validate(ctx context.Context) error {
 
 	{
 		wrap := func(e error) error { return fmt.Errorf("invalid servers: %v", e) }
-		if v := value.Servers; v != nil {
+		if v := doc.Servers; v != nil {
 			if err := v.Validate(ctx); err != nil {
 				return wrap(err)
 			}
@@ -105,7 +108,7 @@ func (value *T) Validate(ctx context.Context) error {
 
 	{
 		wrap := func(e error) error { return fmt.Errorf("invalid tags: %w", e) }
-		if v := value.Tags; v != nil {
+		if v := doc.Tags; v != nil {
 			if err := v.Validate(ctx); err != nil {
 				return wrap(err)
 			}
@@ -114,7 +117,7 @@ func (value *T) Validate(ctx context.Context) error {
 
 	{
 		wrap := func(e error) error { return fmt.Errorf("invalid external docs: %w", e) }
-		if v := value.ExternalDocs; v != nil {
+		if v := doc.ExternalDocs; v != nil {
 			if err := v.Validate(ctx); err != nil {
 				return wrap(err)
 			}
diff --git a/openapi3/operation.go b/openapi3/operation.go
index 29e70c7..58750ff 100644
--- a/openapi3/operation.go
+++ b/openapi3/operation.go
@@ -6,8 +6,9 @@ import (
 	"fmt"
 	"strconv"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
@@ -56,14 +57,17 @@ func NewOperation() *Operation {
 	return &Operation{}
 }
 
+// MarshalJSON returns the JSON encoding of Operation.
 func (operation *Operation) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(operation)
 }
 
+// UnmarshalJSON sets Operation to a copy of data.
 func (operation *Operation) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, operation)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (operation Operation) JSONLookup(token string) (interface{}, error) {
 	switch token {
 	case "requestBody":
@@ -122,25 +126,26 @@ func (operation *Operation) AddResponse(status int, response *Response) {
 	}
 }
 
-func (value *Operation) Validate(ctx context.Context) error {
-	if v := value.Parameters; v != nil {
+// Validate returns an error if Operation does not comply with the OpenAPI spec.
+func (operation *Operation) Validate(ctx context.Context) error {
+	if v := operation.Parameters; v != nil {
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
 	}
-	if v := value.RequestBody; v != nil {
+	if v := operation.RequestBody; v != nil {
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
 	}
-	if v := value.Responses; v != nil {
+	if v := operation.Responses; v != nil {
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
 	} else {
 		return errors.New("value of responses must be an object")
 	}
-	if v := value.ExternalDocs; v != nil {
+	if v := operation.ExternalDocs; v != nil {
 		if err := v.Validate(ctx); err != nil {
 			return fmt.Errorf("invalid external docs: %w", err)
 		}
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index e283a98..7783484 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -6,14 +6,16 @@ import (
 	"fmt"
 	"strconv"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type ParametersMap map[string]*ParameterRef
 
 var _ jsonpointer.JSONPointable = (*ParametersMap)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (p ParametersMap) JSONLookup(token string) (interface{}, error) {
 	ref, ok := p[token]
 	if ref == nil || ok == false {
@@ -31,6 +33,7 @@ type Parameters []*ParameterRef
 
 var _ jsonpointer.JSONPointable = (*Parameters)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (p Parameters) JSONLookup(token string) (interface{}, error) {
 	index, err := strconv.Atoi(token)
 	if err != nil {
@@ -64,9 +67,10 @@ func (parameters Parameters) GetByInAndName(in string, name string) *Parameter {
 	return nil
 }
 
-func (value Parameters) Validate(ctx context.Context) error {
+// Validate returns an error if Parameters does not comply with the OpenAPI spec.
+func (parameters Parameters) Validate(ctx context.Context) error {
 	dupes := make(map[string]struct{})
-	for _, item := range value {
+	for _, item := range parameters {
 		if v := item.Value; v != nil {
 			key := v.In + ":" + v.Name
 			if _, ok := dupes[key]; ok {
@@ -161,50 +165,53 @@ func (parameter *Parameter) WithSchema(value *Schema) *Parameter {
 	return parameter
 }
 
+// MarshalJSON returns the JSON encoding of Parameter.
 func (parameter *Parameter) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(parameter)
 }
 
+// UnmarshalJSON sets Parameter to a copy of data.
 func (parameter *Parameter) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, parameter)
 }
 
-func (value Parameter) JSONLookup(token string) (interface{}, error) {
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
+func (parameter Parameter) JSONLookup(token string) (interface{}, error) {
 	switch token {
 	case "schema":
-		if value.Schema != nil {
-			if value.Schema.Ref != "" {
-				return &Ref{Ref: value.Schema.Ref}, nil
+		if parameter.Schema != nil {
+			if parameter.Schema.Ref != "" {
+				return &Ref{Ref: parameter.Schema.Ref}, nil
 			}
-			return value.Schema.Value, nil
+			return parameter.Schema.Value, nil
 		}
 	case "name":
-		return value.Name, nil
+		return parameter.Name, nil
 	case "in":
-		return value.In, nil
+		return parameter.In, nil
 	case "description":
-		return value.Description, nil
+		return parameter.Description, nil
 	case "style":
-		return value.Style, nil
+		return parameter.Style, nil
 	case "explode":
-		return value.Explode, nil
+		return parameter.Explode, nil
 	case "allowEmptyValue":
-		return value.AllowEmptyValue, nil
+		return parameter.AllowEmptyValue, nil
 	case "allowReserved":
-		return value.AllowReserved, nil
+		return parameter.AllowReserved, nil
 	case "deprecated":
-		return value.Deprecated, nil
+		return parameter.Deprecated, nil
 	case "required":
-		return value.Required, nil
+		return parameter.Required, nil
 	case "example":
-		return value.Example, nil
+		return parameter.Example, nil
 	case "examples":
-		return value.Examples, nil
+		return parameter.Examples, nil
 	case "content":
-		return value.Content, nil
+		return parameter.Content, nil
 	}
 
-	v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token)
+	v, _, err := jsonpointer.GetForToken(parameter.ExtensionProps, token)
 	return v, err
 }
 
@@ -238,11 +245,12 @@ func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error)
 	}
 }
 
-func (value *Parameter) Validate(ctx context.Context) error {
-	if value.Name == "" {
+// Validate returns an error if Parameter does not comply with the OpenAPI spec.
+func (parameter *Parameter) Validate(ctx context.Context) error {
+	if parameter.Name == "" {
 		return errors.New("parameter name can't be blank")
 	}
-	in := value.In
+	in := parameter.In
 	switch in {
 	case
 		ParameterInPath,
@@ -250,60 +258,60 @@ func (value *Parameter) Validate(ctx context.Context) error {
 		ParameterInHeader,
 		ParameterInCookie:
 	default:
-		return fmt.Errorf("parameter can't have 'in' value %q", value.In)
+		return fmt.Errorf("parameter can't have 'in' value %q", parameter.In)
 	}
 
-	if in == ParameterInPath && !value.Required {
-		return fmt.Errorf("path parameter %q must be required", value.Name)
+	if in == ParameterInPath && !parameter.Required {
+		return fmt.Errorf("path parameter %q must be required", parameter.Name)
 	}
 
 	// Validate a parameter's serialization method.
-	sm, err := value.SerializationMethod()
+	sm, err := parameter.SerializationMethod()
 	if err != nil {
 		return err
 	}
 	var smSupported bool
 	switch {
-	case value.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
-		value.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
-		value.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
-		value.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
-		value.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
-		value.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
-
-		value.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
-		value.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
-		value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
-		value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
-		value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
-		value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
-		value.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
-
-		value.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
-		value.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
-
-		value.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
-		value.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
+	case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode,
+		parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode,
+
+		parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode,
+		parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode,
+
+		parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode,
+		parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode,
+
+		parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode,
+		parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode:
 		smSupported = true
 	}
 	if !smSupported {
 		e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in)
-		return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
+		return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e)
 	}
 
-	if (value.Schema == nil) == (value.Content == nil) {
+	if (parameter.Schema == nil) == (parameter.Content == nil) {
 		e := errors.New("parameter must contain exactly one of content and schema")
-		return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e)
+		return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e)
 	}
-	if schema := value.Schema; schema != nil {
+	if schema := parameter.Schema; schema != nil {
 		if err := schema.Validate(ctx); err != nil {
-			return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, err)
+			return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, err)
 		}
 	}
 
-	if content := value.Content; content != nil {
+	if content := parameter.Content; content != nil {
 		if err := content.Validate(ctx); err != nil {
-			return fmt.Errorf("parameter %q content is invalid: %v", value.Name, err)
+			return fmt.Errorf("parameter %q content is invalid: %v", parameter.Name, err)
 		}
 	}
 	return nil
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 4473d63..6a8cc73 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -29,10 +29,12 @@ type PathItem struct {
 	Parameters  Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of PathItem.
 func (pathItem *PathItem) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(pathItem)
 }
 
+// UnmarshalJSON sets PathItem to a copy of data.
 func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, pathItem)
 }
@@ -119,8 +121,9 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
 	}
 }
 
-func (value *PathItem) Validate(ctx context.Context) error {
-	for _, operation := range value.Operations() {
+// Validate returns an error if PathItem does not comply with the OpenAPI spec.
+func (pathItem *PathItem) Validate(ctx context.Context) error {
+	for _, operation := range pathItem.Operations() {
 		if err := operation.Validate(ctx); err != nil {
 			return err
 		}
diff --git a/openapi3/paths.go b/openapi3/paths.go
index 24ab5f3..b4ebe58 100644
--- a/openapi3/paths.go
+++ b/openapi3/paths.go
@@ -10,16 +10,17 @@ import (
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
 type Paths map[string]*PathItem
 
-func (value Paths) Validate(ctx context.Context) error {
+// Validate returns an error if Paths does not comply with the OpenAPI spec.
+func (paths Paths) Validate(ctx context.Context) error {
 	normalizedPaths := make(map[string]string)
-	for path, pathItem := range value {
+	for path, pathItem := range paths {
 		if path == "" || path[0] != '/' {
 			return fmt.Errorf("path %q does not start with a forward slash (/)", path)
 		}
 
 		if pathItem == nil {
-			value[path] = &PathItem{}
-			pathItem = value[path]
+			paths[path] = &PathItem{}
+			pathItem = paths[path]
 		}
 
 		normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
@@ -84,7 +85,7 @@ func (value Paths) Validate(ctx context.Context) error {
 		}
 	}
 
-	if err := value.validateUniqueOperationIDs(); err != nil {
+	if err := paths.validateUniqueOperationIDs(); err != nil {
 		return err
 	}
 
@@ -120,9 +121,9 @@ func (paths Paths) Find(key string) *PathItem {
 	return nil
 }
 
-func (value Paths) validateUniqueOperationIDs() error {
+func (paths Paths) validateUniqueOperationIDs() error {
 	operationIDs := make(map[string]string)
-	for urlPath, pathItem := range value {
+	for urlPath, pathItem := range paths {
 		if pathItem == nil {
 			continue
 		}
diff --git a/openapi3/refs.go b/openapi3/refs.go
index 333cd17..0bf737f 100644
--- a/openapi3/refs.go
+++ b/openapi3/refs.go
@@ -3,8 +3,9 @@ package openapi3
 import (
 	"context"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Ref is specified by OpenAPI/Swagger 3.0 standard.
@@ -22,14 +23,17 @@ type CallbackRef struct {
 
 var _ jsonpointer.JSONPointable = (*CallbackRef)(nil)
 
+// MarshalJSON returns the JSON encoding of CallbackRef.
 func (value *CallbackRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets CallbackRef to a copy of data.
 func (value *CallbackRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if CallbackRef does not comply with the OpenAPI spec.
 func (value *CallbackRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -37,6 +41,7 @@ func (value *CallbackRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value CallbackRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
@@ -55,14 +60,17 @@ type ExampleRef struct {
 
 var _ jsonpointer.JSONPointable = (*ExampleRef)(nil)
 
+// MarshalJSON returns the JSON encoding of ExampleRef.
 func (value *ExampleRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets ExampleRef to a copy of data.
 func (value *ExampleRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if ExampleRef does not comply with the OpenAPI spec.
 func (value *ExampleRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -70,6 +78,7 @@ func (value *ExampleRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value ExampleRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
@@ -88,14 +97,17 @@ type HeaderRef struct {
 
 var _ jsonpointer.JSONPointable = (*HeaderRef)(nil)
 
+// MarshalJSON returns the JSON encoding of HeaderRef.
 func (value *HeaderRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets HeaderRef to a copy of data.
 func (value *HeaderRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if HeaderRef does not comply with the OpenAPI spec.
 func (value *HeaderRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -103,6 +115,7 @@ func (value *HeaderRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value HeaderRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
@@ -119,14 +132,17 @@ type LinkRef struct {
 	Value *Link
 }
 
+// MarshalJSON returns the JSON encoding of LinkRef.
 func (value *LinkRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets LinkRef to a copy of data.
 func (value *LinkRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if LinkRef does not comply with the OpenAPI spec.
 func (value *LinkRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -143,14 +159,17 @@ type ParameterRef struct {
 
 var _ jsonpointer.JSONPointable = (*ParameterRef)(nil)
 
+// MarshalJSON returns the JSON encoding of ParameterRef.
 func (value *ParameterRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets ParameterRef to a copy of data.
 func (value *ParameterRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if ParameterRef does not comply with the OpenAPI spec.
 func (value *ParameterRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -158,6 +177,7 @@ func (value *ParameterRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value ParameterRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
@@ -176,14 +196,17 @@ type ResponseRef struct {
 
 var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
 
+// MarshalJSON returns the JSON encoding of ResponseRef.
 func (value *ResponseRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets ResponseRef to a copy of data.
 func (value *ResponseRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if ResponseRef does not comply with the OpenAPI spec.
 func (value *ResponseRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -191,6 +214,7 @@ func (value *ResponseRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value ResponseRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
@@ -209,14 +233,17 @@ type RequestBodyRef struct {
 
 var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
 
+// MarshalJSON returns the JSON encoding of RequestBodyRef.
 func (value *RequestBodyRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets RequestBodyRef to a copy of data.
 func (value *RequestBodyRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec.
 func (value *RequestBodyRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -224,6 +251,7 @@ func (value *RequestBodyRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value RequestBodyRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
@@ -249,14 +277,17 @@ func NewSchemaRef(ref string, value *Schema) *SchemaRef {
 	}
 }
 
+// MarshalJSON returns the JSON encoding of SchemaRef.
 func (value *SchemaRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets SchemaRef to a copy of data.
 func (value *SchemaRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if SchemaRef does not comply with the OpenAPI spec.
 func (value *SchemaRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -264,6 +295,7 @@ func (value *SchemaRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value SchemaRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
@@ -282,14 +314,17 @@ type SecuritySchemeRef struct {
 
 var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil)
 
+// MarshalJSON returns the JSON encoding of SecuritySchemeRef.
 func (value *SecuritySchemeRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
 }
 
+// UnmarshalJSON sets SecuritySchemeRef to a copy of data.
 func (value *SecuritySchemeRef) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
 }
 
+// Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec.
 func (value *SecuritySchemeRef) Validate(ctx context.Context) error {
 	if v := value.Value; v != nil {
 		return v.Validate(ctx)
@@ -297,6 +332,7 @@ func (value *SecuritySchemeRef) Validate(ctx context.Context) error {
 	return foundUnresolvedRef(value.Ref)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (value SecuritySchemeRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
 		return value.Ref, nil
diff --git a/openapi3/request_body.go b/openapi3/request_body.go
index 0be098c..c97563a 100644
--- a/openapi3/request_body.go
+++ b/openapi3/request_body.go
@@ -5,14 +5,16 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type RequestBodies map[string]*RequestBodyRef
 
 var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (r RequestBodies) JSONLookup(token string) (interface{}, error) {
 	ref, ok := r[token]
 	if ok == false {
@@ -92,17 +94,20 @@ func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType {
 	return m[mediaType]
 }
 
+// MarshalJSON returns the JSON encoding of RequestBody.
 func (requestBody *RequestBody) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(requestBody)
 }
 
+// UnmarshalJSON sets RequestBody to a copy of data.
 func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, requestBody)
 }
 
-func (value *RequestBody) Validate(ctx context.Context) error {
-	if value.Content == nil {
+// Validate returns an error if RequestBody does not comply with the OpenAPI spec.
+func (requestBody *RequestBody) Validate(ctx context.Context) error {
+	if requestBody.Content == nil {
 		return errors.New("content of the request body is required")
 	}
-	return value.Content.Validate(ctx)
+	return requestBody.Content.Validate(ctx)
 }
diff --git a/openapi3/response.go b/openapi3/response.go
index 8e22698..31ea257 100644
--- a/openapi3/response.go
+++ b/openapi3/response.go
@@ -6,8 +6,9 @@ import (
 	"fmt"
 	"strconv"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Responses is specified by OpenAPI/Swagger 3.0 standard.
@@ -30,11 +31,12 @@ func (responses Responses) Get(status int) *ResponseRef {
 	return responses[strconv.FormatInt(int64(status), 10)]
 }
 
-func (value Responses) Validate(ctx context.Context) error {
-	if len(value) == 0 {
+// Validate returns an error if Responses does not comply with the OpenAPI spec.
+func (responses Responses) Validate(ctx context.Context) error {
+	if len(responses) == 0 {
 		return errors.New("the responses object MUST contain at least one response code")
 	}
-	for _, v := range value {
+	for _, v := range responses {
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
@@ -42,6 +44,7 @@ func (value Responses) Validate(ctx context.Context) error {
 	return nil
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (responses Responses) JSONLookup(token string) (interface{}, error) {
 	ref, ok := responses[token]
 	if ok == false {
@@ -89,31 +92,34 @@ func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response {
 	return response
 }
 
+// MarshalJSON returns the JSON encoding of Response.
 func (response *Response) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(response)
 }
 
+// UnmarshalJSON sets Response to a copy of data.
 func (response *Response) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, response)
 }
 
-func (value *Response) Validate(ctx context.Context) error {
-	if value.Description == nil {
+// Validate returns an error if Response does not comply with the OpenAPI spec.
+func (response *Response) Validate(ctx context.Context) error {
+	if response.Description == nil {
 		return errors.New("a short description of the response is required")
 	}
 
-	if content := value.Content; content != nil {
+	if content := response.Content; content != nil {
 		if err := content.Validate(ctx); err != nil {
 			return err
 		}
 	}
-	for _, header := range value.Headers {
+	for _, header := range response.Headers {
 		if err := header.Validate(ctx); err != nil {
 			return err
 		}
 	}
 
-	for _, link := range value.Links {
+	for _, link := range response.Links {
 		if err := link.Validate(ctx); err != nil {
 			return err
 		}
diff --git a/openapi3/schema.go b/openapi3/schema.go
index ee21bc2..4c8df1c 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -12,8 +12,9 @@ import (
 	"strconv"
 	"unicode/utf16"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 const (
@@ -67,6 +68,7 @@ type Schemas map[string]*SchemaRef
 
 var _ jsonpointer.JSONPointable = (*Schemas)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (s Schemas) JSONLookup(token string) (interface{}, error) {
 	ref, ok := s[token]
 	if ref == nil || ok == false {
@@ -83,6 +85,7 @@ type SchemaRefs []*SchemaRef
 
 var _ jsonpointer.JSONPointable = (*SchemaRefs)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (s SchemaRefs) JSONLookup(token string) (interface{}, error) {
 	i, err := strconv.ParseUint(token, 10, 64)
 	if err != nil {
@@ -164,14 +167,17 @@ func NewSchema() *Schema {
 	return &Schema{}
 }
 
+// MarshalJSON returns the JSON encoding of Schema.
 func (schema *Schema) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(schema)
 }
 
+// UnmarshalJSON sets Schema to a copy of data.
 func (schema *Schema) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, schema)
 }
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (schema Schema) JSONLookup(token string) (interface{}, error) {
 	switch token {
 	case "additionalProperties":
@@ -588,8 +594,9 @@ func (schema *Schema) IsEmpty() bool {
 	return true
 }
 
-func (value *Schema) Validate(ctx context.Context) error {
-	return value.validate(ctx, []*Schema{})
+// Validate returns an error if Schema does not comply with the OpenAPI spec.
+func (schema *Schema) Validate(ctx context.Context) error {
+	return schema.validate(ctx, []*Schema{})
 }
 
 func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) {
@@ -1352,6 +1359,19 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 		return schema.expectedType(settings, TypeObject)
 	}
 
+	if settings.asreq || settings.asrep {
+		for propName, propSchema := range schema.Properties {
+			if value[propName] == nil {
+				if dlft := propSchema.Value.Default; dlft != nil {
+					value[propName] = dlft
+					if f := settings.defaultsSet; f != nil {
+						settings.onceSettingDefaults.Do(f)
+					}
+				}
+			}
+		}
+	}
+
 	var me MultiError
 
 	// "properties"
diff --git a/openapi3/schema_formats.go b/openapi3/schema_formats.go
index 29fbd51..17b7564 100644
--- a/openapi3/schema_formats.go
+++ b/openapi3/schema_formats.go
@@ -12,18 +12,19 @@ const (
 	FormatOfStringForUUIDOfRFC4122 = `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$`
 )
 
-//FormatCallback custom check on exotic formats
-type FormatCallback func(Val string) error
+// FormatCallback performs custom checks on exotic formats
+type FormatCallback func(value string) error
 
+// Format represents a format validator registered by either DefineStringFormat or DefineStringFormatCallback
 type Format struct {
 	regexp   *regexp.Regexp
 	callback FormatCallback
 }
 
-//SchemaStringFormats allows for validating strings format
-var SchemaStringFormats = make(map[string]Format, 8)
+// SchemaStringFormats allows for validating string formats
+var SchemaStringFormats = make(map[string]Format, 4)
 
-//DefineStringFormat Defines a new regexp pattern for a given format
+// DefineStringFormat defines a new regexp pattern for a given format
 func DefineStringFormat(name string, pattern string) {
 	re, err := regexp.Compile(pattern)
 	if err != nil {
diff --git a/openapi3/schema_validation_settings.go b/openapi3/schema_validation_settings.go
index 71db5f2..cb4c142 100644
--- a/openapi3/schema_validation_settings.go
+++ b/openapi3/schema_validation_settings.go
@@ -1,5 +1,9 @@
 package openapi3
 
+import (
+	"sync"
+)
+
 // SchemaValidationOption describes options a user has when validating request / response bodies.
 type SchemaValidationOption func(*schemaValidationSettings)
 
@@ -7,6 +11,9 @@ type schemaValidationSettings struct {
 	failfast     bool
 	multiError   bool
 	asreq, asrep bool // exclusive (XOR) fields
+
+	onceSettingDefaults sync.Once
+	defaultsSet         func()
 }
 
 // FailFast returns schema validation errors quicker.
@@ -25,6 +32,11 @@ func VisitAsResponse() SchemaValidationOption {
 	return func(s *schemaValidationSettings) { s.asreq, s.asrep = false, true }
 }
 
+// DefaultsSet executes the given callback (once) IFF schema validation set default values.
+func DefaultsSet(f func()) SchemaValidationOption {
+	return func(s *schemaValidationSettings) { s.defaultsSet = f }
+}
+
 func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
 	settings := &schemaValidationSettings{}
 	for _, opt := range opts {
diff --git a/openapi3/security_requirements.go b/openapi3/security_requirements.go
index df6b6b2..42d8325 100644
--- a/openapi3/security_requirements.go
+++ b/openapi3/security_requirements.go
@@ -15,8 +15,9 @@ func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *
 	return srs
 }
 
-func (value SecurityRequirements) Validate(ctx context.Context) error {
-	for _, item := range value {
+// Validate returns an error if SecurityRequirements does not comply with the OpenAPI spec.
+func (srs SecurityRequirements) Validate(ctx context.Context) error {
+	for _, item := range srs {
 		if err := item.Validate(ctx); err != nil {
 			return err
 		}
@@ -40,6 +41,7 @@ func (security SecurityRequirement) Authenticate(provider string, scopes ...stri
 	return security
 }
 
-func (value SecurityRequirement) Validate(ctx context.Context) error {
+// Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec.
+func (security *SecurityRequirement) Validate(ctx context.Context) error {
 	return nil
 }
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index 9b89fb9..52c3ef2 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -5,12 +5,14 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/go-openapi/jsonpointer"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type SecuritySchemes map[string]*SecuritySchemeRef
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (s SecuritySchemes) JSONLookup(token string) (interface{}, error) {
 	ref, ok := s[token]
 	if ref == nil || ok == false {
@@ -67,10 +69,12 @@ func NewJWTSecurityScheme() *SecurityScheme {
 	}
 }
 
+// MarshalJSON returns the JSON encoding of SecurityScheme.
 func (ss *SecurityScheme) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(ss)
 }
 
+// UnmarshalJSON sets SecurityScheme to a copy of data.
 func (ss *SecurityScheme) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, ss)
 }
@@ -105,15 +109,16 @@ func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme {
 	return ss
 }
 
-func (value *SecurityScheme) Validate(ctx context.Context) error {
+// Validate returns an error if SecurityScheme does not comply with the OpenAPI spec.
+func (ss *SecurityScheme) Validate(ctx context.Context) error {
 	hasIn := false
 	hasBearerFormat := false
 	hasFlow := false
-	switch value.Type {
+	switch ss.Type {
 	case "apiKey":
 		hasIn = true
 	case "http":
-		scheme := value.Scheme
+		scheme := ss.Scheme
 		switch scheme {
 		case "bearer":
 			hasBearerFormat = true
@@ -124,46 +129,46 @@ func (value *SecurityScheme) Validate(ctx context.Context) error {
 	case "oauth2":
 		hasFlow = true
 	case "openIdConnect":
-		if value.OpenIdConnectUrl == "" {
-			return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", value.Name)
+		if ss.OpenIdConnectUrl == "" {
+			return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", ss.Name)
 		}
 	default:
-		return fmt.Errorf("security scheme 'type' can't be %q", value.Type)
+		return fmt.Errorf("security scheme 'type' can't be %q", ss.Type)
 	}
 
 	// Validate "in" and "name"
 	if hasIn {
-		switch value.In {
+		switch ss.In {
 		case "query", "header", "cookie":
 		default:
-			return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", value.In)
+			return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", ss.In)
 		}
-		if value.Name == "" {
+		if ss.Name == "" {
 			return errors.New("security scheme of type 'apiKey' should have 'name'")
 		}
-	} else if len(value.In) > 0 {
-		return fmt.Errorf("security scheme of type %q can't have 'in'", value.Type)
-	} else if len(value.Name) > 0 {
+	} else if len(ss.In) > 0 {
+		return fmt.Errorf("security scheme of type %q can't have 'in'", ss.Type)
+	} else if len(ss.Name) > 0 {
 		return errors.New("security scheme of type 'apiKey' can't have 'name'")
 	}
 
 	// Validate "format"
 	// "bearerFormat" is an arbitrary string so we only check if the scheme supports it
-	if !hasBearerFormat && len(value.BearerFormat) > 0 {
-		return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", value.Type)
+	if !hasBearerFormat && len(ss.BearerFormat) > 0 {
+		return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", ss.Type)
 	}
 
 	// Validate "flow"
 	if hasFlow {
-		flow := value.Flows
+		flow := ss.Flows
 		if flow == nil {
-			return fmt.Errorf("security scheme of type %q should have 'flows'", value.Type)
+			return fmt.Errorf("security scheme of type %q should have 'flows'", ss.Type)
 		}
 		if err := flow.Validate(ctx); err != nil {
 			return fmt.Errorf("security scheme 'flow' is invalid: %v", err)
 		}
-	} else if value.Flows != nil {
-		return fmt.Errorf("security scheme of type %q can't have 'flows'", value.Type)
+	} else if ss.Flows != nil {
+		return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type)
 	}
 	return nil
 }
@@ -188,14 +193,17 @@ const (
 	oAuthFlowAuthorizationCode
 )
 
+// MarshalJSON returns the JSON encoding of OAuthFlows.
 func (flows *OAuthFlows) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(flows)
 }
 
+// UnmarshalJSON sets OAuthFlows to a copy of data.
 func (flows *OAuthFlows) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, flows)
 }
 
+// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
 func (flows *OAuthFlows) Validate(ctx context.Context) error {
 	if v := flows.Implicit; v != nil {
 		return v.Validate(ctx, oAuthFlowTypeImplicit)
@@ -223,14 +231,17 @@ type OAuthFlow struct {
 	Scopes           map[string]string `json:"scopes" yaml:"scopes"`
 }
 
+// MarshalJSON returns the JSON encoding of OAuthFlow.
 func (flow *OAuthFlow) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(flow)
 }
 
+// UnmarshalJSON sets OAuthFlow to a copy of data.
 func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, flow)
 }
 
+// Validate returns an error if OAuthFlow does not comply with the OpenAPI spec.
 func (flow *OAuthFlow) Validate(ctx context.Context, typ oAuthFlowType) error {
 	if typ == oAuthFlowAuthorizationCode || typ == oAuthFlowTypeImplicit {
 		if v := flow.AuthorizationURL; v == "" {
diff --git a/openapi3/server.go b/openapi3/server.go
index 94092a6..478f8ff 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -14,9 +14,9 @@ import (
 // Servers is specified by OpenAPI/Swagger standard version 3.
 type Servers []*Server
 
-// Validate ensures servers are per the OpenAPIv3 specification.
-func (value Servers) Validate(ctx context.Context) error {
-	for _, v := range value {
+// Validate returns an error if Servers does not comply with the OpenAPI spec.
+func (servers Servers) Validate(ctx context.Context) error {
+	for _, v := range servers {
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
@@ -48,10 +48,12 @@ type Server struct {
 	Variables   map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of Server.
 func (server *Server) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(server)
 }
 
+// UnmarshalJSON sets Server to a copy of data.
 func (server *Server) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, server)
 }
@@ -127,19 +129,20 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) {
 	return params, input, true
 }
 
-func (value *Server) Validate(ctx context.Context) (err error) {
-	if value.URL == "" {
+// Validate returns an error if Server does not comply with the OpenAPI spec.
+func (server *Server) Validate(ctx context.Context) (err error) {
+	if server.URL == "" {
 		return errors.New("value of url must be a non-empty string")
 	}
-	opening, closing := strings.Count(value.URL, "{"), strings.Count(value.URL, "}")
+	opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}")
 	if opening != closing {
 		return errors.New("server URL has mismatched { and }")
 	}
-	if opening != len(value.Variables) {
+	if opening != len(server.Variables) {
 		return errors.New("server has undeclared variables")
 	}
-	for name, v := range value.Variables {
-		if !strings.Contains(value.URL, fmt.Sprintf("{%s}", name)) {
+	for name, v := range server.Variables {
+		if !strings.Contains(server.URL, fmt.Sprintf("{%s}", name)) {
 			return errors.New("server has undeclared variables")
 		}
 		if err = v.Validate(ctx); err != nil {
@@ -159,17 +162,20 @@ type ServerVariable struct {
 	Description string   `json:"description,omitempty" yaml:"description,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of ServerVariable.
 func (serverVariable *ServerVariable) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(serverVariable)
 }
 
+// UnmarshalJSON sets ServerVariable to a copy of data.
 func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, serverVariable)
 }
 
-func (value *ServerVariable) Validate(ctx context.Context) error {
-	if value.Default == "" {
-		data, err := value.MarshalJSON()
+// Validate returns an error if ServerVariable does not comply with the OpenAPI spec.
+func (serverVariable *ServerVariable) Validate(ctx context.Context) error {
+	if serverVariable.Default == "" {
+		data, err := serverVariable.MarshalJSON()
 		if err != nil {
 			return err
 		}
diff --git a/openapi3/tag.go b/openapi3/tag.go
index 6aa9a1e..8fb5ac3 100644
--- a/openapi3/tag.go
+++ b/openapi3/tag.go
@@ -19,6 +19,7 @@ func (tags Tags) Get(name string) *Tag {
 	return nil
 }
 
+// Validate returns an error if Tags does not comply with the OpenAPI spec.
 func (tags Tags) Validate(ctx context.Context) error {
 	for _, v := range tags {
 		if err := v.Validate(ctx); err != nil {
@@ -38,14 +39,17 @@ type Tag struct {
 	ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
 }
 
+// MarshalJSON returns the JSON encoding of Tag.
 func (t *Tag) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalStrictStruct(t)
 }
 
+// UnmarshalJSON sets Tag to a copy of data.
 func (t *Tag) UnmarshalJSON(data []byte) error {
 	return jsoninfo.UnmarshalStrictStruct(data, t)
 }
 
+// Validate returns an error if Tag does not comply with the OpenAPI spec.
 func (t *Tag) Validate(ctx context.Context) error {
 	if v := t.ExternalDocs; v != nil {
 		if err := v.Validate(ctx); err != nil {
diff --git a/openapi3/xml.go b/openapi3/xml.go
index 8fd2abd..03686ad 100644
--- a/openapi3/xml.go
+++ b/openapi3/xml.go
@@ -18,14 +18,17 @@ type XML struct {
 	Wrapped   bool   `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
 }
 
-func (value *XML) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(value)
+// MarshalJSON returns the JSON encoding of XML.
+func (xml *XML) MarshalJSON() ([]byte, error) {
+	return jsoninfo.MarshalStrictStruct(xml)
 }
 
-func (value *XML) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, value)
+// UnmarshalJSON sets XML to a copy of data.
+func (xml *XML) UnmarshalJSON(data []byte) error {
+	return jsoninfo.UnmarshalStrictStruct(data, xml)
 }
 
-func (value *XML) Validate(ctx context.Context) error {
+// Validate returns an error if XML does not comply with the OpenAPI spec.
+func (xml *XML) Validate(ctx context.Context) error {
 	return nil // TODO
 }
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 0408d8d..a54b3bb 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -14,7 +14,7 @@ import (
 	"strconv"
 	"strings"
 
-	"gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v3"
 
 	"github.com/getkin/kin-openapi/openapi3"
 )
@@ -795,19 +795,19 @@ func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error)
 	case "integer":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid integer", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "number":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "boolean":
 		v, err := strconv.ParseBool(raw)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "string":
@@ -868,7 +868,11 @@ const prefixUnsupportedCT = "unsupported content type"
 
 // decodeBody returns a decoded body.
 // The function returns ParseError when a body is invalid.
-func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (
+	string,
+	interface{},
+	error,
+) {
 	contentType := header.Get(headerCT)
 	if contentType == "" {
 		if _, ok := body.(*multipart.Part); ok {
@@ -878,16 +882,16 @@ func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef,
 	mediaType := parseMediaType(contentType)
 	decoder, ok := bodyDecoders[mediaType]
 	if !ok {
-		return nil, &ParseError{
+		return "", nil, &ParseError{
 			Kind:   KindUnsupportedFormat,
 			Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType),
 		}
 	}
 	value, err := decoder(body, header, schema, encFn)
 	if err != nil {
-		return nil, err
+		return "", nil, err
 	}
-	return value, nil
+	return mediaType, value, nil
 }
 
 func init() {
@@ -1036,7 +1040,7 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 		}
 
 		var value interface{}
-		if value, err = decodeBody(part, http.Header(part.Header), valueSchema, subEncFn); err != nil {
+		if _, value, err = decodeBody(part, http.Header(part.Header), valueSchema, subEncFn); err != nil {
 			if v, ok := err.(*ParseError); ok {
 				return nil, &ParseError{path: []interface{}{name}, Cause: v}
 			}
diff --git a/openapi3filter/req_resp_encoder.go b/openapi3filter/req_resp_encoder.go
new file mode 100644
index 0000000..dd410f5
--- /dev/null
+++ b/openapi3filter/req_resp_encoder.go
@@ -0,0 +1,23 @@
+package openapi3filter
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+func encodeBody(body interface{}, mediaType string) ([]byte, error) {
+	encoder, ok := bodyEncoders[mediaType]
+	if !ok {
+		return nil, &ParseError{
+			Kind:   KindUnsupportedFormat,
+			Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType),
+		}
+	}
+	return encoder(body)
+}
+
+type bodyEncoder func(body interface{}) ([]byte, error)
+
+var bodyEncoders = map[string]bodyEncoder{
+	"application/json": json.Marshal,
+}
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index fae6b09..4f22326 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -142,6 +142,29 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param
 		}
 		schema = parameter.Schema.Value
 	}
+
+	// Set default value if needed
+	if value == nil && schema != nil && schema.Default != nil {
+		value = schema.Default
+		req := input.Request
+		switch parameter.In {
+		// case openapi3.ParameterInPath: TODO: no idea how to handle this
+		case openapi3.ParameterInQuery:
+			q := req.URL.Query()
+			q.Add(parameter.Name, fmt.Sprintf("%v", value))
+			req.URL.RawQuery = q.Encode()
+		case openapi3.ParameterInHeader:
+			req.Header.Add(parameter.Name, fmt.Sprintf("%v", value))
+		case openapi3.ParameterInCookie:
+			req.AddCookie(&http.Cookie{
+				Name:  parameter.Name,
+				Value: fmt.Sprintf("%v", value),
+			})
+		default:
+			return fmt.Errorf("unsupported parameter's 'in': %s", parameter.In)
+		}
+	}
+
 	// Validate a parameter's value and presence.
 	if parameter.Required && !found {
 		return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidRequired.Error(), Err: ErrInvalidRequired}
@@ -230,7 +253,7 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 	}
 
 	encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
-	value, err := decodeBody(bytes.NewReader(data), req.Header, contentType.Schema, encFn)
+	mediaType, value, err := decodeBody(bytes.NewReader(data), req.Header, contentType.Schema, encFn)
 	if err != nil {
 		return &RequestError{
 			Input:       input,
@@ -240,8 +263,10 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 		}
 	}
 
-	opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here
+	defaultsSet := false
+	opts := make([]openapi3.SchemaValidationOption, 0, 3) // 3 potential opts here
 	opts = append(opts, openapi3.VisitAsRequest())
+	opts = append(opts, openapi3.DefaultsSet(func() { defaultsSet = true }))
 	if options.MultiError {
 		opts = append(opts, openapi3.MultiErrors())
 	}
@@ -255,6 +280,21 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 			Err:         err,
 		}
 	}
+
+	if defaultsSet {
+		var err error
+		if data, err = encodeBody(value, mediaType); err != nil {
+			return &RequestError{
+				Input:       input,
+				RequestBody: requestBody,
+				Reason:      "rewriting failed",
+				Err:         err,
+			}
+		}
+		// Put the data back into the input
+		req.Body = ioutil.NopCloser(bytes.NewReader(data))
+	}
+
 	return nil
 }
 
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index 7cb713a..f19123e 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -111,7 +111,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 	input.SetBodyBytes(data)
 
 	encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
-	value, err := decodeBody(bytes.NewBuffer(data), input.Header, contentType.Schema, encFn)
+	_, value, err := decodeBody(bytes.NewBuffer(data), input.Header, contentType.Schema, encFn)
 	if err != nil {
 		return &ResponseError{
 			Input:  input,
diff --git a/openapi3gen/openapi3gen.go b/openapi3gen/openapi3gen.go
index f2a45c4..4387727 100644
--- a/openapi3gen/openapi3gen.go
+++ b/openapi3gen/openapi3gen.go
@@ -18,6 +18,11 @@ type CycleError struct{}
 
 func (err *CycleError) Error() string { return "detected cycle" }
 
+// ExcludeSchemaSentinel indicates that the schema for a specific field should not be included in the final output.
+type ExcludeSchemaSentinel struct{}
+
+func (err *ExcludeSchemaSentinel) Error() string { return "schema excluded" }
+
 // Option allows tweaking SchemaRef generation
 type Option func(*generatorOpt)
 
@@ -25,7 +30,10 @@ type Option func(*generatorOpt)
 // the OpenAPI schema definition to be updated with additional
 // properties during the generation process, based on the
 // name of the field, the Go type, and the struct tags.
-// name will be "_root" for the top level object, and tag will be ""
+// name will be "_root" for the top level object, and tag will be "".
+// A SchemaCustomizerFn can return an ExcludeSchemaSentinel error to
+// indicate that the schema for this field should not be included in
+// the final output
 type SchemaCustomizerFn func(name string, t reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error
 
 type generatorOpt struct {
@@ -117,6 +125,10 @@ func (g *Generator) generateSchemaRefFor(parents []*jsoninfo.TypeInfo, t reflect
 		return ref, nil
 	}
 	ref, err := g.generateWithoutSaving(parents, t, name, tag)
+	if _, ok := err.(*ExcludeSchemaSentinel); ok {
+		// This schema should not be included in the final output
+		return nil, nil
+	}
 	if err != nil {
 		return nil, err
 	}
diff --git a/routers/gorillamux/router.go b/routers/gorillamux/router.go
index 83bbf82..bf551a7 100644
--- a/routers/gorillamux/router.go
+++ b/routers/gorillamux/router.go
@@ -9,44 +9,89 @@ package gorillamux
 import (
 	"net/http"
 	"net/url"
+	"regexp"
 	"sort"
 	"strings"
 
+	"github.com/gorilla/mux"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/routers"
-	"github.com/gorilla/mux"
 )
 
 var _ routers.Router = &Router{}
 
 // Router helps link http.Request.s and an OpenAPIv3 spec
 type Router struct {
-	muxes  []*mux.Route
+	muxes  []routeMux
 	routes []*routers.Route
 }
 
+type varsf func(vars map[string]string)
+
+type routeMux struct {
+	muxRoute    *mux.Route
+	varsUpdater varsf
+}
+
+var singleVariableMatcher = regexp.MustCompile(`^\{([^{}]+)\}$`)
+
+// TODO: Handle/HandlerFunc + ServeHTTP (When there is a match, the route variables can be retrieved calling mux.Vars(request))
+
 // NewRouter creates a gorilla/mux router.
 // Assumes spec is .Validate()d
-// TODO: Handle/HandlerFunc + ServeHTTP (When there is a match, the route variables can be retrieved calling mux.Vars(request))
+// Note that a variable for the port number MUST have a default value and only this value will match as the port (see issue #367).
 func NewRouter(doc *openapi3.T) (routers.Router, error) {
 	type srv struct {
-		schemes    []string
-		host, base string
-		server     *openapi3.Server
+		schemes     []string
+		host, base  string
+		server      *openapi3.Server
+		varsUpdater varsf
 	}
 	servers := make([]srv, 0, len(doc.Servers))
 	for _, server := range doc.Servers {
 		serverURL := server.URL
+		if submatch := singleVariableMatcher.FindStringSubmatch(serverURL); submatch != nil {
+			sVar := submatch[1]
+			sVal := server.Variables[sVar].Default
+			serverURL = strings.ReplaceAll(serverURL, "{"+sVar+"}", sVal)
+			var varsUpdater varsf
+			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
+		}
+
 		var schemes []string
-		var u *url.URL
-		var err error
 		if strings.Contains(serverURL, "://") {
 			scheme0 := strings.Split(serverURL, "://")[0]
 			schemes = permutePart(scheme0, server)
-			u, err = url.Parse(bEncode(strings.Replace(serverURL, scheme0+"://", schemes[0]+"://", 1)))
-		} else {
-			u, err = url.Parse(bEncode(serverURL))
+			serverURL = strings.Replace(serverURL, scheme0+"://", schemes[0]+"://", 1)
+		}
+
+		// If a variable represents the port "http://domain.tld:{port}/bla"
+		// then url.Parse() cannot parse "http://domain.tld:`bEncode({port})`/bla"
+		// and mux is not able to set the {port} variable
+		// So we just use the default value for this variable.
+		// See https://github.com/getkin/kin-openapi/issues/367
+		var varsUpdater varsf
+		if lhs := strings.Index(serverURL, ":{"); lhs > 0 {
+			rest := serverURL[lhs+len(":{"):]
+			rhs := strings.Index(rest, "}")
+			portVariable := rest[:rhs]
+			portValue := server.Variables[portVariable].Default
+			serverURL = strings.ReplaceAll(serverURL, "{"+portVariable+"}", portValue)
+			varsUpdater = func(vars map[string]string) {
+				vars[portVariable] = portValue
+			}
 		}
+
+		u, err := url.Parse(bEncode(serverURL))
 		if err != nil {
 			return nil, err
 		}
@@ -55,10 +100,11 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
 			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,
+			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,
 		})
 	}
 	if len(servers) == 0 {
@@ -87,7 +133,10 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
 			if err := muxRoute.GetError(); err != nil {
 				return nil, err
 			}
-			r.muxes = append(r.muxes, muxRoute)
+			r.muxes = append(r.muxes, routeMux{
+				muxRoute:    muxRoute,
+				varsUpdater: s.varsUpdater,
+			})
 			r.routes = append(r.routes, &routers.Route{
 				Spec:      doc,
 				Server:    s.server,
@@ -103,16 +152,20 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
 
 // FindRoute extracts the route and parameters of an http.Request
 func (r *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) {
-	for i, muxRoute := range r.muxes {
+	for i, m := range r.muxes {
 		var match mux.RouteMatch
-		if muxRoute.Match(req, &match) {
+		if m.muxRoute.Match(req, &match) {
 			if err := match.MatchErr; err != nil {
 				// What then?
 			}
+			vars := match.Vars
+			if f := m.varsUpdater; f != nil {
+				f(vars)
+			}
 			route := *r.routes[i]
 			route.Method = req.Method
 			route.Operation = route.Spec.Paths[route.Path].GetOperation(route.Method)
-			return &route, match.Vars, nil
+			return &route, vars, nil
 		}
 		switch match.MatchErr {
 		case nil:


Test patch:
diff --git a/openapi2/openapi2_test.go b/openapi2/openapi2_test.go
index 8d3efdf..65e92d6 100644
--- a/openapi2/openapi2_test.go
+++ b/openapi2/openapi2_test.go
@@ -6,8 +6,9 @@ import (
 	"io/ioutil"
 	"reflect"
 
+	"github.com/invopop/yaml"
+
 	"github.com/getkin/kin-openapi/openapi2"
-	"github.com/ghodss/yaml"
 )
 
 func Example() {
diff --git a/openapi2conv/issue187_test.go b/openapi2conv/issue187_test.go
index 1c113b7..93914d9 100644
--- a/openapi2conv/issue187_test.go
+++ b/openapi2conv/issue187_test.go
@@ -5,10 +5,11 @@ import (
 	"encoding/json"
 	"testing"
 
+	"github.com/invopop/yaml"
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi2"
 	"github.com/getkin/kin-openapi/openapi3"
-	"github.com/ghodss/yaml"
-	"github.com/stretchr/testify/require"
 )
 
 func v2v3JSON(spec2 []byte) (doc3 *openapi3.T, err error) {
@@ -103,7 +104,7 @@ func TestIssue187(t *testing.T) {
 	spec3, err := json.Marshal(doc3)
 	require.NoError(t, err)
 	const expected = `{"components":{"schemas":{"model.ProductSearchAttributeRequest":{"properties":{"filterField":{"type":"string"},"filterKey":{"type":"string"},"type":{"type":"string"},"values":{"$ref":"#/components/schemas/model.ProductSearchAttributeValueRequest"}},"title":"model.ProductSearchAttributeRequest","type":"object"},"model.ProductSearchAttributeValueRequest":{"properties":{"imageUrl":{"type":"string"},"text":{"type":"string"}},"title":"model.ProductSearchAttributeValueRequest","type":"object"}}},"info":{"contact":{"email":"test@test.com","name":"Test"},"description":"Test Golang Application","title":"Test","version":"1.0"},"openapi":"3.0.3","paths":{"/me":{"get":{"operationId":"someTest","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/model.ProductSearchAttributeRequest"}}},"description":"successful operation"}},"summary":"Some test","tags":["probe"]}}}}`
-	require.Equal(t, string(spec3), expected)
+	require.JSONEq(t, string(spec3), expected)
 
 	err = doc3.Validate(context.Background())
 	require.NoError(t, err)
@@ -162,7 +163,7 @@ paths:
         "200":
           description: description
 `
-	require.Equal(t, string(spec3), expected)
+	require.YAMLEq(t, string(spec3), expected)
 
 	err = doc3.Validate(context.Background())
 	require.NoError(t, err)
diff --git a/openapi2conv/issue440_test.go b/openapi2conv/issue440_test.go
index 24f7a29..2478384 100644
--- a/openapi2conv/issue440_test.go
+++ b/openapi2conv/issue440_test.go
@@ -6,9 +6,10 @@ import (
 	"os"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi2"
 	"github.com/getkin/kin-openapi/openapi3"
-	"github.com/stretchr/testify/require"
 )
 
 func TestIssue440(t *testing.T) {
diff --git a/openapi3/extension_test.go b/openapi3/extension_test.go
index 9d00902..a995378 100644
--- a/openapi3/extension_test.go
+++ b/openapi3/extension_test.go
@@ -5,8 +5,9 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 func ExampleExtensionProps_DecodeWith() {
diff --git a/openapi3/internalize_refs_test.go b/openapi3/internalize_refs_test.go
index d6264d4..b1ca846 100644
--- a/openapi3/internalize_refs_test.go
+++ b/openapi3/internalize_refs_test.go
@@ -2,6 +2,7 @@ package openapi3
 
 import (
 	"context"
+	"io/ioutil"
 	"regexp"
 	"testing"
 
@@ -9,8 +10,10 @@ import (
 )
 
 func TestInternalizeRefs(t *testing.T) {
-	var regexpRef = regexp.MustCompile(`"\$ref":`)
-	var regexpRefInternal = regexp.MustCompile(`"\$ref":"\#`)
+	ctx := context.Background()
+
+	regexpRef := regexp.MustCompile(`"\$ref":`)
+	regexpRefInternal := regexp.MustCompile(`"\$ref":"#`)
 
 	tests := []struct {
 		filename string
@@ -28,13 +31,15 @@ func TestInternalizeRefs(t *testing.T) {
 			sl.IsExternalRefsAllowed = true
 			doc, err := sl.LoadFromFile(test.filename)
 			require.NoError(t, err, "loading test file")
+			err = doc.Validate(ctx)
+			require.NoError(t, err, "validating spec")
 
 			// Internalize the references
-			doc.InternalizeRefs(context.Background(), DefaultRefNameResolver)
+			doc.InternalizeRefs(ctx, nil)
 
 			// Validate the internalized spec
-			err = doc.Validate(context.Background())
-			require.Nil(t, err, "validating internalized spec")
+			err = doc.Validate(ctx)
+			require.NoError(t, err, "validating internalized spec")
 
 			data, err := doc.MarshalJSON()
 			require.NoError(t, err, "marshalling internalized spec")
@@ -48,8 +53,13 @@ func TestInternalizeRefs(t *testing.T) {
 			// load from data, but with the path set to the current directory
 			doc2, err := sl.LoadFromData(data)
 			require.NoError(t, err, "reloading spec")
-			err = doc2.Validate(context.Background())
-			require.Nil(t, err, "validating reloaded spec")
+			err = doc2.Validate(ctx)
+			require.NoError(t, err, "validating reloaded spec")
+
+			// compare with expected
+			data0, err := ioutil.ReadFile(test.filename + ".internalized.yml")
+			require.NoError(t, err)
+			require.JSONEq(t, string(data), string(data0))
 		})
 	}
 }
diff --git a/openapi3/loader_test.go b/openapi3/loader_test.go
index 384e54d..e33b75d 100644
--- a/openapi3/loader_test.go
+++ b/openapi3/loader_test.go
@@ -438,6 +438,20 @@ paths:
 	require.Equal(t, "link to to the father", link.Description)
 }
 
+func TestLinksFromOAISpec(t *testing.T) {
+	loader := NewLoader()
+	doc, err := loader.LoadFromFile("testdata/link-example.yaml")
+	require.NoError(t, err)
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+	response := doc.Paths[`/2.0/repositories/{username}/{slug}`].Get.Responses.Get(200).Value
+	link := response.Links[`repositoryPullRequests`].Value
+	require.Equal(t, map[string]interface{}{
+		"username": "$response.body#/owner/username",
+		"slug":     "$response.body#/slug",
+	}, link.Parameters)
+}
+
 func TestResolveNonComponentsRef(t *testing.T) {
 	spec := []byte(`
 openapi: 3.0.0
diff --git a/openapi3/openapi3_test.go b/openapi3/openapi3_test.go
index 69a2f95..7736310 100644
--- a/openapi3/openapi3_test.go
+++ b/openapi3/openapi3_test.go
@@ -6,7 +6,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/ghodss/yaml"
+	"github.com/invopop/yaml"
 	"github.com/stretchr/testify/require"
 )
 
diff --git a/openapi3/race_test.go b/openapi3/race_test.go
index 4ac31c3..c617cfe 100644
--- a/openapi3/race_test.go
+++ b/openapi3/race_test.go
@@ -4,8 +4,9 @@ import (
 	"context"
 	"testing"
 
-	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
 )
 
 func TestRaceyPatternSchema(t *testing.T) {
diff --git a/openapi3/schema_formats_test.go b/openapi3/schema_formats_test.go
index 14733c8..5cceb8c 100644
--- a/openapi3/schema_formats_test.go
+++ b/openapi3/schema_formats_test.go
@@ -13,6 +13,9 @@ func TestIssue430(t *testing.T) {
 		NewStringSchema().WithFormat("ipv6"),
 	)
 
+	delete(SchemaStringFormats, "ipv4")
+	delete(SchemaStringFormats, "ipv6")
+
 	err := schema.Validate(context.Background())
 	require.NoError(t, err)
 
diff --git a/openapi3/testdata/callbacks.yml.internalized.yml b/openapi3/testdata/callbacks.yml.internalized.yml
new file mode 100644
index 0000000..866cb5c
--- /dev/null
+++ b/openapi3/testdata/callbacks.yml.internalized.yml
@@ -0,0 +1,131 @@
+{
+  "components": {
+    "callbacks": {
+      "MyCallbackEvent": {
+        "{$request.query.queryUrl}": {
+          "post": {
+            "requestBody": {
+              "content": {
+                "application/json": {
+                  "schema": {
+                    "$ref": "#/components/schemas/SomeOtherPayload"
+                  }
+                }
+              },
+              "description": "Callback payload"
+            },
+            "responses": {
+              "200": {
+                "description": "callback successfully processed"
+              }
+            }
+          }
+        }
+      }
+    },
+    "schemas": {
+      "SomeOtherPayload": {
+        "type": "boolean"
+      },
+      "SomePayload": {
+        "type": "object"
+      }
+    }
+  },
+  "info": {
+    "title": "Callback refd",
+    "version": "1.2.3"
+  },
+  "openapi": "3.1.0",
+  "paths": {
+    "/other": {
+      "post": {
+        "callbacks": {
+          "myEvent": {
+            "$ref": "#/components/callbacks/MyCallbackEvent"
+          }
+        },
+        "parameters": [
+          {
+            "description": "bla\nbla\nbla\n",
+            "in": "query",
+            "name": "queryUrl",
+            "required": true,
+            "schema": {
+              "example": "https://example.com",
+              "format": "uri",
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "201": {
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object"
+                }
+              }
+            },
+            "description": ""
+          }
+        }
+      }
+    },
+    "/trans": {
+      "post": {
+        "callbacks": {
+          "transactionCallback": {
+            "http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}": {
+              "post": {
+                "requestBody": {
+                  "content": {
+                    "application/json": {
+                      "schema": {
+                        "$ref": "#/components/schemas/SomePayload"
+                      }
+                    }
+                  },
+                  "description": "Callback payload"
+                },
+                "responses": {
+                  "200": {
+                    "description": "callback successfully processed"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "email": {
+                    "format": "email"
+                  },
+                  "id": {
+                    "type": "string"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object"
+                }
+              }
+            },
+            "description": "subscription successfully created"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/openapi3/testdata/link-example.yaml b/openapi3/testdata/link-example.yaml
new file mode 100644
index 0000000..5837d70
--- /dev/null
+++ b/openapi3/testdata/link-example.yaml
@@ -0,0 +1,203 @@
+openapi: 3.0.0
+info: 
+  title: Link Example
+  version: 1.0.0
+paths: 
+  /2.0/users/{username}: 
+    get: 
+      operationId: getUserByName
+      parameters: 
+      - name: username
+        in: path
+        required: true
+        schema:
+          type: string
+      responses: 
+        '200':
+          description: The User
+          content:
+            application/json:
+              schema: 
+                $ref: '#/components/schemas/user'
+          links:
+            userRepositories:
+              $ref: '#/components/links/UserRepositories'
+  /2.0/repositories/{username}:
+    get:
+      operationId: getRepositoriesByOwner
+      parameters:
+        - name: username
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        '200':
+          description: repositories owned by the supplied user
+          content: 
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/repository'
+          links:
+            userRepository:
+              $ref: '#/components/links/UserRepository'
+  /2.0/repositories/{username}/{slug}: 
+    get: 
+      operationId: getRepository
+      parameters: 
+        - name: username
+          in: path
+          required: true
+          schema:
+            type: string
+        - name: slug
+          in: path
+          required: true
+          schema:
+            type: string
+      responses: 
+        '200':
+          description: The repository
+          content:
+              application/json: 
+                schema: 
+                  $ref: '#/components/schemas/repository'
+          links:
+            repositoryPullRequests:
+              $ref: '#/components/links/RepositoryPullRequests'
+  /2.0/repositories/{username}/{slug}/pullrequests: 
+    get: 
+      operationId: getPullRequestsByRepository
+      parameters: 
+      - name: username
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: slug
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: state
+        in: query
+        schema:
+          type: string
+          enum: 
+            - open
+            - merged
+            - declined
+      responses: 
+        '200':
+          description: an array of pull request objects
+          content:
+            application/json: 
+              schema: 
+                type: array
+                items: 
+                  $ref: '#/components/schemas/pullrequest'
+  /2.0/repositories/{username}/{slug}/pullrequests/{pid}: 
+    get: 
+      operationId: getPullRequestsById
+      parameters: 
+      - name: username
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: slug
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: pid
+        in: path
+        required: true
+        schema:
+          type: string
+      responses: 
+        '200':
+          description: a pull request object
+          content:
+            application/json: 
+              schema: 
+                $ref: '#/components/schemas/pullrequest'
+          links:
+            pullRequestMerge:
+              $ref: '#/components/links/PullRequestMerge'
+  /2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge: 
+    post: 
+      operationId: mergePullRequest
+      parameters: 
+      - name: username
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: slug
+        in: path
+        required: true
+        schema:
+          type: string
+      - name: pid
+        in: path
+        required: true
+        schema:
+          type: string
+      responses: 
+        '204':
+          description: the PR was successfully merged
+components:
+  links:
+    UserRepositories:
+      # returns array of '#/components/schemas/repository'
+      operationId: getRepositoriesByOwner
+      parameters:
+        username: $response.body#/username
+    UserRepository:
+      # returns '#/components/schemas/repository'
+      operationId: getRepository
+      parameters:
+        username: $response.body#/owner/username
+        slug: $response.body#/slug
+    RepositoryPullRequests:
+      # returns '#/components/schemas/pullrequest'
+      operationId: getPullRequestsByRepository
+      parameters: 
+          username: $response.body#/owner/username
+          slug: $response.body#/slug
+    PullRequestMerge:
+      # executes /2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge
+      operationId: mergePullRequest
+      parameters:
+        username: $response.body#/author/username
+        slug: $response.body#/repository/slug
+        pid: $response.body#/id
+  schemas: 
+    user: 
+      type: object
+      properties: 
+        username: 
+          type: string
+        uuid: 
+          type: string
+    repository: 
+      type: object
+      properties: 
+        slug: 
+          type: string
+        owner: 
+          $ref: '#/components/schemas/user'
+    pullrequest: 
+      type: object
+      properties: 
+        id: 
+          type: integer
+        title: 
+          type: string
+        repository: 
+          $ref: '#/components/schemas/repository'
+        author: 
+          $ref: '#/components/schemas/user'
diff --git a/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml
new file mode 100644
index 0000000..d1260eb
--- /dev/null
+++ b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml
@@ -0,0 +1,68 @@
+{
+  "components": {
+    "parameters": {
+      "number": {
+        "in": "query",
+        "name": "someNumber",
+        "schema": {
+          "type": "string"
+        }
+      }
+    },
+    "schemas": {
+      "Bar": {
+        "example": "bar",
+        "type": "string"
+      },
+      "Foo": {
+        "properties": {
+          "bar": {
+            "$ref": "#/components/schemas/Bar"
+          }
+        },
+        "type": "object"
+      },
+      "Foo2": {
+        "properties": {
+          "foo": {
+            "$ref": "#/components/schemas/Foo"
+          }
+        },
+        "type": "object"
+      }
+    }
+  },
+  "info": {
+    "title": "Recursive refs example",
+    "version": "1.0"
+  },
+  "openapi": "3.0.3",
+  "paths": {
+    "/foo": {
+      "get": {
+        "responses": {
+          "200": {
+            "content": {
+              "application/json": {
+                "schema": {
+                  "properties": {
+                    "foo2": {
+                      "$ref": "#/components/schemas/Foo2"
+                    }
+                  },
+                  "type": "object"
+                }
+              }
+            },
+            "description": "OK"
+          }
+        }
+      },
+      "parameters": [
+        {
+          "$ref": "#/components/parameters/number"
+        }
+      ]
+    }
+  }
+}
diff --git a/openapi3/testdata/recursiveRef/parameters/number.yml b/openapi3/testdata/recursiveRef/parameters/number.yml
new file mode 100644
index 0000000..29f0f26
--- /dev/null
+++ b/openapi3/testdata/recursiveRef/parameters/number.yml
@@ -0,0 +1,4 @@
+name: someNumber
+in: query
+schema:
+  type: string
diff --git a/openapi3/testdata/recursiveRef/paths/foo.yml b/openapi3/testdata/recursiveRef/paths/foo.yml
index 1653c7a..43e03b7 100644
--- a/openapi3/testdata/recursiveRef/paths/foo.yml
+++ b/openapi3/testdata/recursiveRef/paths/foo.yml
@@ -1,3 +1,5 @@
+parameters:
+  - $ref: ../parameters/number.yml
 get:
   responses:
     "200":
diff --git a/openapi3/testdata/spec.yaml.internalized.yml b/openapi3/testdata/spec.yaml.internalized.yml
new file mode 100644
index 0000000..feca4a0
--- /dev/null
+++ b/openapi3/testdata/spec.yaml.internalized.yml
@@ -0,0 +1,36 @@
+{
+  "components": {
+    "schemas": {
+      "Test": {
+        "properties": {
+          "test": {
+            "$ref": "#/components/schemas/b"
+          }
+        },
+        "type": "object"
+      },
+      "a": {
+        "type": "string"
+      },
+      "b": {
+        "description": "I use a local reference.",
+        "properties": {
+          "name": {
+            "$ref": "#/components/schemas/a"
+          }
+        },
+        "type": "object"
+      }
+    }
+  },
+  "info": {
+    "license": {
+      "name": "MIT"
+    },
+    "title": "Some Swagger",
+    "version": "1.0.0"
+  },
+  "openapi": "3.0.1",
+  "paths": {
+  }
+}
diff --git a/openapi3/testdata/testref.openapi.yml.internalized.yml b/openapi3/testdata/testref.openapi.yml.internalized.yml
new file mode 100644
index 0000000..e35a500
--- /dev/null
+++ b/openapi3/testdata/testref.openapi.yml.internalized.yml
@@ -0,0 +1,19 @@
+{
+  "components": {
+    "schemas": {
+      "AnotherTestSchema": {
+        "type": "string"
+      },
+      "CustomTestSchema": {
+        "type": "string"
+      }
+    }
+  },
+  "info": {
+    "title": "OAI Specification w/ refs in YAML",
+    "version": "1"
+  },
+  "openapi": "3.0.0",
+  "paths": {
+  }
+}
diff --git a/openapi3/unique_items_checker_test.go b/openapi3/unique_items_checker_test.go
index 85147c6..270b797 100644
--- a/openapi3/unique_items_checker_test.go
+++ b/openapi3/unique_items_checker_test.go
@@ -4,8 +4,9 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
 )
 
 func TestRegisterArrayUniqueItemsChecker(t *testing.T) {
diff --git a/openapi3filter/middleware_test.go b/openapi3filter/middleware_test.go
index 930af2a..c6a5e9b 100644
--- a/openapi3filter/middleware_test.go
+++ b/openapi3filter/middleware_test.go
@@ -383,7 +383,6 @@ func ExampleValidator() {
 	// OpenAPI specification for a simple service that squares integers, with
 	// some limitations.
 	doc, err := openapi3.NewLoader().LoadFromData([]byte(`
-info:
 openapi: 3.0.0
 info:
   title: 'Validator - square example'
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index de93547..8bd62b1 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -15,9 +15,10 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
-	"github.com/stretchr/testify/require"
 )
 
 func TestDecodeParameter(t *testing.T) {
@@ -1280,7 +1281,7 @@ func TestDecodeBody(t *testing.T) {
 				}
 				return tc.encoding[name]
 			}
-			got, err := decodeBody(tc.body, h, schemaRef, encFn)
+			_, got, err := decodeBody(tc.body, h, schemaRef, encFn)
 
 			if tc.wantErr != nil {
 				require.Error(t, err)
@@ -1350,7 +1351,7 @@ func TestRegisterAndUnregisterBodyDecoder(t *testing.T) {
 	body := strings.NewReader("foo,bar")
 	schema := openapi3.NewArraySchema().WithItems(openapi3.NewStringSchema()).NewRef()
 	encFn := func(string) *openapi3.Encoding { return nil }
-	got, err := decodeBody(body, h, schema, encFn)
+	_, got, err := decodeBody(body, h, schema, encFn)
 
 	require.NoError(t, err)
 	require.Equal(t, []string{"foo", "bar"}, got)
@@ -1360,7 +1361,7 @@ func TestRegisterAndUnregisterBodyDecoder(t *testing.T) {
 	originalDecoder = RegisteredBodyDecoder(contentType)
 	require.Nil(t, originalDecoder)
 
-	_, err = decodeBody(body, h, schema, encFn)
+	_, _, err = decodeBody(body, h, schema, encFn)
 	require.Equal(t, &ParseError{
 		Kind:   KindUnsupportedFormat,
 		Reason: prefixUnsupportedCT + ` "text/csv"`,
diff --git a/openapi3filter/validate_readonly_test.go b/openapi3filter/validate_readonly_test.go
index 454a927..1152ec8 100644
--- a/openapi3filter/validate_readonly_test.go
+++ b/openapi3filter/validate_readonly_test.go
@@ -6,9 +6,10 @@ import (
 	"net/http"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
-	"github.com/stretchr/testify/require"
 )
 
 func TestValidatingRequestBodyWithReadOnlyProperty(t *testing.T) {
diff --git a/openapi3filter/validate_request_test.go b/openapi3filter/validate_request_test.go
index b43f6c8..957b692 100644
--- a/openapi3filter/validate_request_test.go
+++ b/openapi3filter/validate_request_test.go
@@ -10,11 +10,12 @@ import (
 	"net/http"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/routers"
 	"github.com/getkin/kin-openapi/routers/gorillamux"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
 func setupTestRouter(t *testing.T, spec string) routers.Router {
diff --git a/openapi3filter/validate_set_default_test.go b/openapi3filter/validate_set_default_test.go
new file mode 100644
index 0000000..4071405
--- /dev/null
+++ b/openapi3filter/validate_set_default_test.go
@@ -0,0 +1,562 @@
+package openapi3filter
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
+)
+
+func TestValidatingRequestParameterAndSetDefault(t *testing.T) {
+	const spec = `{
+  "openapi": "3.0.3",
+  "info": {
+    "version": "1.0.0",
+    "title": "title",
+    "description": "desc",
+    "contact": {
+      "email": "email"
+    }
+  },
+  "paths": {
+    "/accounts": {
+      "get": {
+        "description": "Create a new account",
+        "parameters": [
+          {
+            "in": "query",
+            "name": "q1",
+            "schema": {
+              "type": "string",
+              "default": "Q"
+            }
+          },
+          {
+            "in": "query",
+            "name": "q2",
+            "schema": {
+              "type": "string",
+              "default": "Q"
+            }
+          },
+          {
+            "in": "query",
+            "name": "q3",
+            "schema": {
+              "type": "string"
+            }
+          },
+          {
+            "in": "header",
+            "name": "h1",
+            "schema": {
+              "type": "boolean",
+              "default": true
+            }
+          },
+          {
+            "in": "header",
+            "name": "h2",
+            "schema": {
+              "type": "boolean",
+              "default": true
+            }
+          },
+          {
+            "in": "header",
+            "name": "h3",
+            "schema": {
+              "type": "boolean"
+            }
+          },
+          {
+            "in": "cookie",
+            "name": "c1",
+            "schema": {
+              "type": "integer",
+              "default": 128
+            }
+          },
+          {
+            "in": "cookie",
+            "name": "c2",
+            "schema": {
+              "type": "integer",
+              "default": 128
+            }
+          },
+          {
+            "in": "cookie",
+            "name": "c3",
+            "schema": {
+              "type": "integer"
+            }
+          }
+        ],
+        "responses": {
+          "201": {
+            "description": "Successfully created a new account"
+          },
+          "400": {
+            "description": "The server could not understand the request due to invalid syntax",
+          }
+        }
+      }
+    }
+  }
+}
+`
+
+	sl := openapi3.NewLoader()
+	doc, err := sl.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+	err = doc.Validate(sl.Context)
+	require.NoError(t, err)
+	router, err := legacyrouter.NewRouter(doc)
+	require.NoError(t, err)
+
+	httpReq, err := http.NewRequest(http.MethodGet, "/accounts", nil)
+	require.NoError(t, err)
+
+	params := &url.Values{
+		"q2": []string{"from_request"},
+	}
+	httpReq.URL.RawQuery = params.Encode()
+	httpReq.Header.Set("h2", "false")
+	httpReq.AddCookie(&http.Cookie{Name: "c2", Value: "1024"})
+
+	route, pathParams, err := router.FindRoute(httpReq)
+	require.NoError(t, err)
+
+	err = ValidateRequest(sl.Context, &RequestValidationInput{
+		Request:    httpReq,
+		PathParams: pathParams,
+		Route:      route,
+	})
+	require.NoError(t, err)
+
+	// Unset default values in URL were set
+	require.Equal(t, "Q", httpReq.URL.Query().Get("q1"))
+	// Unset default values in headers were set
+	require.Equal(t, "true", httpReq.Header.Get("h1"))
+	// Unset default values in cookies were set
+	cookie, err := httpReq.Cookie("c1")
+	require.NoError(t, err)
+	require.Equal(t, "128", cookie.Value)
+
+	// All values from request were retained
+	require.Equal(t, "from_request", httpReq.URL.Query().Get("q2"))
+	require.Equal(t, "false", httpReq.Header.Get("h2"))
+	cookie, err = httpReq.Cookie("c2")
+	require.NoError(t, err)
+	require.Equal(t, "1024", cookie.Value)
+
+	// Not set value to parameters without default value
+	require.Equal(t, "", httpReq.URL.Query().Get("q3"))
+	require.Equal(t, "", httpReq.Header.Get("h3"))
+	_, err = httpReq.Cookie("c3")
+	require.Equal(t, http.ErrNoCookie, err)
+}
+
+func TestValidateRequestBodyAndSetDefault(t *testing.T) {
+	const spec = `{
+  "openapi": "3.0.3",
+  "info": {
+    "version": "1.0.0",
+    "title": "title",
+    "description": "desc",
+    "contact": {
+      "email": "email"
+    }
+  },
+  "paths": {
+    "/accounts": {
+      "post": {
+        "description": "Create a new account",
+        "requestBody": {
+          "required": true,
+          "content": {
+            "application/json": {
+              "schema": {
+                "type": "object",
+                "required": ["id"],
+                "properties": {
+                  "id": {
+                    "type": "string",
+                    "pattern": "[0-9a-v]+$",
+                    "minLength": 20,
+                    "maxLength": 20
+                  },
+                  "name": {
+                    "type": "string",
+                    "default": "default"
+                  },
+                  "code": {
+                    "type": "integer",
+                    "default": 123
+                  },
+                  "all": {
+                    "type": "boolean",
+                    "default": false
+                  },
+                  "page": {
+                    "type": "object",
+                    "properties": {
+                      "num": {
+                        "type": "integer",
+                        "default": 1
+                      },
+                      "size": {
+                        "type": "integer",
+                        "default": 10
+                      },
+                      "order": {
+                        "type": "string",
+                        "enum": ["asc", "desc"],
+                        "default": "desc"
+                      }
+                    }
+                  },
+                  "filters": {
+                    "type": "array",
+                    "nullable": true,
+                    "items": {
+                      "type": "object",
+                      "properties": {
+                        "field": {
+                          "type": "string",
+                          "default": "name"
+                        },
+                        "op": {
+                          "type": "string",
+                          "enum": ["eq", "ne"],
+                          "default": "eq"
+                        },
+                        "value": {
+                          "type": "integer",
+                          "default": 123
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "description": "Successfully created a new account"
+          },
+          "400": {
+            "description": "The server could not understand the request due to invalid syntax",
+          }
+        }
+      }
+    }
+  }
+}`
+	sl := openapi3.NewLoader()
+	doc, err := sl.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+	err = doc.Validate(sl.Context)
+	require.NoError(t, err)
+	router, err := legacyrouter.NewRouter(doc)
+	require.NoError(t, err)
+
+	type page struct {
+		Num   int    `json:"num,omitempty"`
+		Size  int    `json:"size,omitempty"`
+		Order string `json:"order,omitempty"`
+	}
+	type filter struct {
+		Field string `json:"field,omitempty"`
+		OP    string `json:"op,omitempty"`
+		Value int    `json:"value,omitempty"`
+	}
+	type body struct {
+		ID      string   `json:"id,omitempty"`
+		Name    string   `json:"name,omitempty"`
+		Code    int      `json:"code,omitempty"`
+		All     bool     `json:"all,omitempty"`
+		Page    *page    `json:"page,omitempty"`
+		Filters []filter `json:"filters,omitempty"`
+	}
+
+	testCases := []struct {
+		name          string
+		body          body
+		bodyAssertion func(t *testing.T, body string)
+	}{
+		{
+			name: "only id",
+			body: body{
+				ID: "bt6kdc3d0cvp6u8u3ft0",
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "default", "code": 123, "all": false}`, body)
+			},
+		},
+		{
+			name: "id & name",
+			body: body{
+				ID:   "bt6kdc3d0cvp6u8u3ft0",
+				Name: "non-default",
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "non-default", "code": 123, "all": false}`, body)
+			},
+		},
+		{
+			name: "id & name & code",
+			body: body{
+				ID:   "bt6kdc3d0cvp6u8u3ft0",
+				Name: "non-default",
+				Code: 456,
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "non-default", "code": 456, "all": false}`, body)
+			},
+		},
+		{
+			name: "id & name & code & all",
+			body: body{
+				ID:   "bt6kdc3d0cvp6u8u3ft0",
+				Name: "non-default",
+				Code: 456,
+				All:  true,
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `{"id":"bt6kdc3d0cvp6u8u3ft0", "name": "non-default", "code": 456, "all": true}`, body)
+			},
+		},
+		{
+			name: "id & page(num)",
+			body: body{
+				ID: "bt6kdc3d0cvp6u8u3ft0",
+				Page: &page{
+					Num: 10,
+				},
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `
+{
+  "id": "bt6kdc3d0cvp6u8u3ft0",
+  "name": "default",
+  "code": 123,
+  "all": false,
+  "page": {
+    "num": 10,
+    "size": 10,
+    "order": "desc"
+  }
+}
+        `, body)
+			},
+		},
+		{
+			name: "id & page(num & order)",
+			body: body{
+				ID: "bt6kdc3d0cvp6u8u3ft0",
+				Page: &page{
+					Num:   10,
+					Order: "asc",
+				},
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `
+{
+  "id": "bt6kdc3d0cvp6u8u3ft0",
+  "name": "default",
+  "code": 123,
+  "all": false,
+  "page": {
+    "num": 10,
+    "size": 10,
+    "order": "asc"
+  }
+}
+        `, body)
+			},
+		},
+		{
+			name: "id & page & filters(one element and contains field)",
+			body: body{
+				ID: "bt6kdc3d0cvp6u8u3ft0",
+				Page: &page{
+					Num:   10,
+					Order: "asc",
+				},
+				Filters: []filter{
+					{
+						Field: "code",
+					},
+				},
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `
+{
+  "id": "bt6kdc3d0cvp6u8u3ft0",
+  "name": "default",
+  "code": 123,
+  "all": false,
+  "page": {
+    "num": 10,
+    "size": 10,
+    "order": "asc"
+  },
+  "filters": [
+    {
+      "field": "code",
+      "op": "eq",
+      "value": 123
+    }
+  ]
+}
+        `, body)
+			},
+		},
+		{
+			name: "id & page & filters(one element and contains field & op & value)",
+			body: body{
+				ID: "bt6kdc3d0cvp6u8u3ft0",
+				Page: &page{
+					Num:   10,
+					Order: "asc",
+				},
+				Filters: []filter{
+					{
+						Field: "code",
+						OP:    "ne",
+						Value: 456,
+					},
+				},
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `
+{
+  "id": "bt6kdc3d0cvp6u8u3ft0",
+  "name": "default",
+  "code": 123,
+  "all": false,
+  "page": {
+    "num": 10,
+    "size": 10,
+    "order": "asc"
+  },
+  "filters": [
+    {
+      "field": "code",
+      "op": "ne",
+      "value": 456
+    }
+  ]
+}
+        `, body)
+			},
+		},
+		{
+			name: "id & page & filters(multiple elements)",
+			body: body{
+				ID: "bt6kdc3d0cvp6u8u3ft0",
+				Page: &page{
+					Num:   10,
+					Order: "asc",
+				},
+				Filters: []filter{
+					{
+						Value: 456,
+					},
+					{
+						OP: "ne",
+					},
+					{
+						Field: "code",
+						Value: 456,
+					},
+					{
+						OP:    "ne",
+						Value: 789,
+					},
+					{
+						Field: "code",
+						OP:    "ne",
+						Value: 456,
+					},
+				},
+			},
+			bodyAssertion: func(t *testing.T, body string) {
+				require.JSONEq(t, `
+{
+  "id": "bt6kdc3d0cvp6u8u3ft0",
+  "name": "default",
+  "code": 123,
+  "all": false,
+  "page": {
+    "num": 10,
+    "size": 10,
+    "order": "asc"
+  },
+  "filters": [
+    {
+      "field": "name",
+      "op": "eq",
+      "value": 456
+    },
+    {
+      "field": "name",
+      "op": "ne",
+      "value": 123
+    },
+    {
+      "field": "code",
+      "op": "eq",
+      "value": 456
+    },
+    {
+      "field": "name",
+      "op": "ne",
+      "value": 789
+    },
+    {
+      "field": "code",
+      "op": "ne",
+      "value": 456
+    }
+  ]
+}
+        `, body)
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			b, err := json.Marshal(tc.body)
+			require.NoError(t, err)
+			httpReq, err := http.NewRequest(http.MethodPost, "/accounts", bytes.NewReader(b))
+			require.NoError(t, err)
+			httpReq.Header.Add(headerCT, "application/json")
+
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
+
+			err = ValidateRequest(sl.Context, &RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+			})
+			require.NoError(t, err)
+
+			validatedReqBody, err := ioutil.ReadAll(httpReq.Body)
+			require.NoError(t, err)
+			tc.bodyAssertion(t, string(validatedReqBody))
+		})
+	}
+}
diff --git a/openapi3filter/validation_discriminator_test.go b/openapi3filter/validation_discriminator_test.go
index c7d6144..adabf40 100644
--- a/openapi3filter/validation_discriminator_test.go
+++ b/openapi3filter/validation_discriminator_test.go
@@ -5,9 +5,10 @@ import (
 	"net/http"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
-	"github.com/stretchr/testify/require"
 )
 
 func TestValidationWithDiscriminatorSelection(t *testing.T) {
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index aae26e5..b9151b8 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -10,9 +10,10 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/routers"
-	"github.com/stretchr/testify/require"
 )
 
 func newPetstoreRequest(t *testing.T, method, path string, body io.Reader) *http.Request {
@@ -187,7 +188,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 				Title: `parameter "status" in query is required`},
 		},
 		{
-			name: "error - wrong query string parameter type",
+			name: "error - wrong query string parameter type as integer",
 			args: validationArgs{
 				r: newPetstoreRequest(t, http.MethodGet, "/pet/findByIds?ids=1,notAnInt", nil),
 			},
@@ -195,8 +196,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 			wantErrParamIn: "query",
 			// This is a nested ParseError. The outer error is a KindOther with no details.
 			// So we'd need to look at the inner one which is a KindInvalidFormat. So just check the error body.
-			wantErrBody: `parameter "ids" in query has an error: path 1: value notAnInt: an invalid integer: ` +
-				"strconv.ParseFloat: parsing \"notAnInt\": invalid syntax",
+			wantErrBody: `parameter "ids" in query has an error: path 1: value notAnInt: an invalid integer: invalid syntax`,
 			// TODO: Should we treat query params of the wrong type like a 404 instead of a 400?
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
 				Title: `parameter "ids" in query is invalid: notAnInt is an invalid integer`},
diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go
index 2f7cf80..cd1fa89 100644
--- a/openapi3filter/validation_test.go
+++ b/openapi3filter/validation_test.go
@@ -14,9 +14,10 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
-	"github.com/stretchr/testify/require"
 )
 
 type ExampleRequest struct {
diff --git a/openapi3gen/openapi3gen_test.go b/openapi3gen/openapi3gen_test.go
index 3640d4c..a4c2d52 100644
--- a/openapi3gen/openapi3gen_test.go
+++ b/openapi3gen/openapi3gen_test.go
@@ -10,9 +10,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/openapi3gen"
-	"github.com/stretchr/testify/require"
 )
 
 func ExampleGenerator_SchemaRefs() {
@@ -468,6 +469,30 @@ func TestSchemaCustomizerError(t *testing.T) {
 	require.EqualError(t, err, "test error")
 }
 
+func TestSchemaCustomizerExcludeSchema(t *testing.T) {
+	type Bla struct {
+		Str string
+	}
+
+	customizer := openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
+		return nil
+	})
+	schema, err := openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields(), customizer)
+	require.NoError(t, err)
+	require.Equal(t, &openapi3.SchemaRef{Value: &openapi3.Schema{
+		Type: "object",
+		Properties: map[string]*openapi3.SchemaRef{
+			"Str": {Value: &openapi3.Schema{Type: "string"}},
+		}}}, schema)
+
+	customizer = openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
+		return &openapi3gen.ExcludeSchemaSentinel{}
+	})
+	schema, err = openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields(), customizer)
+	require.NoError(t, err)
+	require.Nil(t, schema)
+}
+
 func ExampleNewSchemaRefForValue_recursive() {
 	type RecursiveType struct {
 		Field1     string           `json:"field1"`
diff --git a/routers/gorillamux/router_test.go b/routers/gorillamux/router_test.go
index 90f5c3d..1898db4 100644
--- a/routers/gorillamux/router_test.go
+++ b/routers/gorillamux/router_test.go
@@ -6,9 +6,10 @@ import (
 	"sort"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/routers"
-	"github.com/stretchr/testify/require"
 )
 
 func TestRouter(t *testing.T) {
@@ -72,6 +73,7 @@ func TestRouter(t *testing.T) {
 	}
 
 	expect := func(r routers.Router, method string, uri string, operation *openapi3.Operation, params map[string]string) {
+		t.Helper()
 		req, err := http.NewRequest(method, uri, nil)
 		require.NoError(t, err)
 		route, pathParams, err := r.FindRoute(req)
@@ -163,6 +165,9 @@ func TestRouter(t *testing.T) {
 			"d1":     {Default: "example", Enum: []string{"example"}},
 			"scheme": {Default: "https", Enum: []string{"https", "http"}},
 		}},
+		{URL: "http://127.0.0.1:{port}/api/v1", Variables: map[string]*openapi3.ServerVariable{
+			"port": {Default: "8000"},
+		}},
 	}
 	err = doc.Validate(context.Background())
 	require.NoError(t, err)
@@ -179,6 +184,20 @@ func TestRouter(t *testing.T) {
 		"d1": "domain1",
 		// "scheme": "https", TODO: https://github.com/gorilla/mux/issues/624
 	})
+	expect(r, http.MethodGet, "http://127.0.0.1:8000/api/v1/hello", helloGET, map[string]string{
+		"port": "8000",
+	})
+
+	doc.Servers = []*openapi3.Server{
+		{URL: "{server}", Variables: map[string]*openapi3.ServerVariable{
+			"server": {Default: "/api/v1"},
+		}},
+	}
+	err = doc.Validate(context.Background())
+	require.NoError(t, err)
+	r, err = NewRouter(doc)
+	require.NoError(t, err)
+	expect(r, http.MethodGet, "https://myserver/api/v1/hello", helloGET, nil)
 
 	{
 		uri := "https://www.example.com/api/v1/onlyGET"
@@ -223,6 +242,11 @@ func TestServerPath(t *testing.T) {
 func TestRelativeURL(t *testing.T) {
 	helloGET := &openapi3.Operation{Responses: openapi3.NewResponses()}
 	doc := &openapi3.T{
+		OpenAPI: "3.0.0",
+		Info: &openapi3.Info{
+			Title:   "rel",
+			Version: "1",
+		},
 		Servers: openapi3.Servers{
 			&openapi3.Server{
 				URL: "/api/v1",
@@ -234,6 +258,8 @@ func TestRelativeURL(t *testing.T) {
 			},
 		},
 	}
+	err := doc.Validate(context.Background())
+	require.NoError(t, err)
 	router, err := NewRouter(doc)
 	require.NoError(t, err)
 	req, err := http.NewRequest(http.MethodGet, "https://example.com/api/v1/hello", nil)
diff --git a/routers/issue356_test.go b/routers/issue356_test.go
index 307e52a..3e4b9fa 100644
--- a/routers/issue356_test.go
+++ b/routers/issue356_test.go
@@ -8,12 +8,13 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/openapi3filter"
 	"github.com/getkin/kin-openapi/routers"
 	"github.com/getkin/kin-openapi/routers/gorillamux"
 	"github.com/getkin/kin-openapi/routers/legacy"
-	"github.com/stretchr/testify/require"
 )
 
 func TestIssue356(t *testing.T) {
diff --git a/routers/legacy/issue444_test.go b/routers/legacy/issue444_test.go
index 222ecbb..c1e9b14 100644
--- a/routers/legacy/issue444_test.go
+++ b/routers/legacy/issue444_test.go
@@ -6,10 +6,11 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/openapi3filter"
 	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
-	"github.com/stretchr/testify/require"
 )
 
 func TestIssue444(t *testing.T) {
diff --git a/routers/legacy/router_test.go b/routers/legacy/router_test.go
index bfc7e11..d4779f5 100644
--- a/routers/legacy/router_test.go
+++ b/routers/legacy/router_test.go
@@ -6,9 +6,10 @@ import (
 	"sort"
 	"testing"
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/getkin/kin-openapi/routers"
-	"github.com/stretchr/testify/require"
 )
 
 func TestRouter(t *testing.T) {