Repository: getkin/kin-openapi (go)

Issue:
Marshaling to yaml doesn't inline ExtensionProps
I don't have a runable example handy as I'm basing this off of some results in my work, but I've pasted 2 differing yaml files and how they were generated from `Swagger` objects in these libraries.

### Expected
Results from `openapiv3.Swagger.MarshalJSON` and converting to yaml in https://editor.swagger.io/#
```
components:
  schemas:
    FooBar:
      properties:
        type_url:
          type: string
        value:
          format: byte
          type: string
      type: object
info:
  title: sample
  version: version not set
openapi: 3.0.3
paths: {}
```

### Actual (invalid openapi spec due to the presence of various struct fields in results)
- `extensionprops`
- `value`

Results from a `yaml.Marshal` of an `openapiv3.Swagger`
```
extensionprops: {}
openapi: 3.0.3
components:
  extensionprops: {}
  schemas:
    FooBar:
      ref: ""
      value:
        extensionprops: {}
        type: object
        properties:
          type_url:
            ref: ""
            value:
              extensionprops: {}
              type: string
          value:
            ref: ""
            value:
              extensionprops: {}
              type: string
              format: byte
info:
  extensionprops: {}
  title: sample
  version: version not set
paths: {}
```

### Questions
- Perhaps there's a more appropriate way to use this library and output the resulting yaml?

### How to fix

From what I can tell this libary needs to add additional `yaml` struct tags (`inline`) to the swagger objects to get this functioning correctly (`encoding/json` behaves this way by default). See https://godoc.org/gopkg.in/yaml.v2#Marshal

Adding `inline` to usages of the embedded `ExtensionProps` struct and to usages of the `Schema`, `SecuritySchema`, and `Example` ref types should cover a lot of this, I'm not sure what the full set is.

### Workaround
Something like the following can be done to work around this issue, by leveraging the correct output from `UnmarshalJSON` to get valid yaml output.

```
	jsonBuf, err := v3Spec.MarshalJSON()
	if err != nil {
		return err
	}
	tmp := map[string]interface{}{}
	err = json.Unmarshal(jsonBuf, &tmp)
	if err != nil {
		return err
	}
	return yaml.NewEncoder(w).Encode(tmp)
```

Pull request description:
fix: yaml marshal output
Fix the output when encoding the YAML by:
* Excluding ExtensionProps, the Extensions were already excluded resulting in empty extensionProps only.
* Add custom marshaler to all ref types so it doesn't output both values in the same way the JSON marshaler does.

Fixes #241

Patch:
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..c68529d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,1 @@
+*.yml text eol=lf
diff --git a/openapi3/components.go b/openapi3/components.go
index b7ad11c..02ae458 100644
--- a/openapi3/components.go
+++ b/openapi3/components.go
@@ -12,7 +12,7 @@ import (
 // Components is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#componentsObject
 type Components struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Schemas         Schemas         `json:"schemas,omitempty" yaml:"schemas,omitempty"`
 	Parameters      ParametersMap   `json:"parameters,omitempty" yaml:"parameters,omitempty"`
diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go
index 4cc4df9..28a2148 100644
--- a/openapi3/discriminator.go
+++ b/openapi3/discriminator.go
@@ -9,7 +9,7 @@ import (
 // Discriminator is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminatorObject
 type Discriminator struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	PropertyName string            `json:"propertyName" yaml:"propertyName"`
 	Mapping      map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index 7bdfaeb..bc4985c 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -11,7 +11,7 @@ import (
 // Encoding is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encodingObject
 type Encoding struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	ContentType   string  `json:"contentType,omitempty" yaml:"contentType,omitempty"`
 	Headers       Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
diff --git a/openapi3/example.go b/openapi3/example.go
index 2161b68..561f09b 100644
--- a/openapi3/example.go
+++ b/openapi3/example.go
@@ -30,7 +30,7 @@ func (e Examples) JSONLookup(token string) (interface{}, error) {
 // Example is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#exampleObject
 type Example struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Summary       string      `json:"summary,omitempty" yaml:"summary,omitempty"`
 	Description   string      `json:"description,omitempty" yaml:"description,omitempty"`
diff --git a/openapi3/example_validation.go b/openapi3/example_validation.go
index 4c75e36..fb7a1da 100644
--- a/openapi3/example_validation.go
+++ b/openapi3/example_validation.go
@@ -1,5 +1,16 @@
 package openapi3
 
-func validateExampleValue(input interface{}, schema *Schema) error {
-	return schema.VisitJSON(input, MultiErrors())
+import "context"
+
+func validateExampleValue(ctx context.Context, input interface{}, schema *Schema) error {
+	opts := make([]SchemaValidationOption, 0, 2)
+
+	if vo := getValidationOptions(ctx); vo.examplesValidationAsReq {
+		opts = append(opts, VisitAsRequest())
+	} else if vo.examplesValidationAsRes {
+		opts = append(opts, VisitAsResponse())
+	}
+	opts = append(opts, MultiErrors())
+
+	return schema.VisitJSON(input, opts...)
 }
diff --git a/openapi3/external_docs.go b/openapi3/external_docs.go
index 17a38ee..75ae0d7 100644
--- a/openapi3/external_docs.go
+++ b/openapi3/external_docs.go
@@ -12,7 +12,7 @@ import (
 // ExternalDocs is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
 type ExternalDocs struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Description string `json:"description,omitempty" yaml:"description,omitempty"`
 	URL         string `json:"url,omitempty" yaml:"url,omitempty"`
diff --git a/openapi3/info.go b/openapi3/info.go
index e60f888..fa6593c 100644
--- a/openapi3/info.go
+++ b/openapi3/info.go
@@ -10,7 +10,7 @@ import (
 // Info is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#infoObject
 type Info struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Title          string   `json:"title" yaml:"title"` // Required
 	Description    string   `json:"description,omitempty" yaml:"description,omitempty"`
@@ -58,7 +58,7 @@ func (info *Info) Validate(ctx context.Context) error {
 // Contact is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contactObject
 type Contact struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Name  string `json:"name,omitempty" yaml:"name,omitempty"`
 	URL   string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -83,7 +83,7 @@ func (contact *Contact) Validate(ctx context.Context) error {
 // License is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#licenseObject
 type License struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Name string `json:"name" yaml:"name"` // Required
 	URL  string `json:"url,omitempty" yaml:"url,omitempty"`
diff --git a/openapi3/link.go b/openapi3/link.go
index 7f0c49d..3fb4d78 100644
--- a/openapi3/link.go
+++ b/openapi3/link.go
@@ -30,7 +30,7 @@ var _ jsonpointer.JSONPointable = (*Links)(nil)
 // Link is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#linkObject
 type Link struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	OperationRef string                 `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
 	OperationID  string                 `json:"operationId,omitempty" yaml:"operationId,omitempty"`
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index 1ae7c99..1a9bb51 100644
--- a/openapi3/media_type.go
+++ b/openapi3/media_type.go
@@ -14,7 +14,7 @@ import (
 // MediaType is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject
 type MediaType struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Schema   *SchemaRef           `json:"schema,omitempty" yaml:"schema,omitempty"`
 	Example  interface{}          `json:"example,omitempty" yaml:"example,omitempty"`
@@ -88,12 +88,12 @@ func (mediaType *MediaType) Validate(ctx context.Context) error {
 			return errors.New("example and examples are mutually exclusive")
 		}
 
-		if validationOpts := getValidationOptions(ctx); validationOpts.ExamplesValidationDisabled {
+		if vo := getValidationOptions(ctx); vo.ExamplesValidationDisabled {
 			return nil
 		}
 
 		if example := mediaType.Example; example != nil {
-			if err := validateExampleValue(example, schema.Value); err != nil {
+			if err := validateExampleValue(ctx, example, schema.Value); err != nil {
 				return fmt.Errorf("invalid example: %w", err)
 			}
 		}
@@ -109,7 +109,7 @@ func (mediaType *MediaType) Validate(ctx context.Context) error {
 				if err := v.Validate(ctx); err != nil {
 					return fmt.Errorf("example %s: %w", k, err)
 				}
-				if err := validateExampleValue(v.Value.Value, schema.Value); err != nil {
+				if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
 					return fmt.Errorf("example %s: %w", k, err)
 				}
 			}
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index 963ef72..510df09 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -11,7 +11,7 @@ import (
 // T is the root of an OpenAPI v3 document
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oasObject
 type T struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	OpenAPI      string               `json:"openapi" yaml:"openapi"` // Required
 	Components   Components           `json:"components,omitempty" yaml:"components,omitempty"`
diff --git a/openapi3/operation.go b/openapi3/operation.go
index 8323394..3abc3c4 100644
--- a/openapi3/operation.go
+++ b/openapi3/operation.go
@@ -14,7 +14,7 @@ import (
 // Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
 type Operation struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	// Optional tags for documentation.
 	Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 73a39a5..dc82a49 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -90,7 +90,7 @@ func (parameters Parameters) Validate(ctx context.Context) error {
 // Parameter is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameterObject
 type Parameter struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Name            string      `json:"name,omitempty" yaml:"name,omitempty"`
 	In              string      `json:"in,omitempty" yaml:"in,omitempty"`
@@ -318,11 +318,12 @@ func (parameter *Parameter) Validate(ctx context.Context) error {
 		if parameter.Example != nil && parameter.Examples != nil {
 			return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name)
 		}
-		if validationOpts := getValidationOptions(ctx); validationOpts.ExamplesValidationDisabled {
+
+		if vo := getValidationOptions(ctx); vo.ExamplesValidationDisabled {
 			return nil
 		}
 		if example := parameter.Example; example != nil {
-			if err := validateExampleValue(example, schema.Value); err != nil {
+			if err := validateExampleValue(ctx, example, schema.Value); err != nil {
 				return fmt.Errorf("invalid example: %w", err)
 			}
 		} else if examples := parameter.Examples; examples != nil {
@@ -336,7 +337,7 @@ func (parameter *Parameter) Validate(ctx context.Context) error {
 				if err := v.Validate(ctx); err != nil {
 					return fmt.Errorf("%s: %w", k, err)
 				}
-				if err := validateExampleValue(v.Value.Value, schema.Value); err != nil {
+				if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
 					return fmt.Errorf("%s: %w", k, err)
 				}
 			}
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 940b159..28ee4b8 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -12,7 +12,7 @@ import (
 // PathItem is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#pathItemObject
 type PathItem struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Ref         string     `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 	Summary     string     `json:"summary,omitempty" yaml:"summary,omitempty"`
diff --git a/openapi3/refs.go b/openapi3/refs.go
index 0bf737f..e85f37e 100644
--- a/openapi3/refs.go
+++ b/openapi3/refs.go
@@ -23,6 +23,11 @@ type CallbackRef struct {
 
 var _ jsonpointer.JSONPointable = (*CallbackRef)(nil)
 
+// MarshalYAML returns the YAML encoding of CallbackRef.
+func (value *CallbackRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of CallbackRef.
 func (value *CallbackRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -60,6 +65,11 @@ type ExampleRef struct {
 
 var _ jsonpointer.JSONPointable = (*ExampleRef)(nil)
 
+// MarshalYAML returns the YAML encoding of ExampleRef.
+func (value *ExampleRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of ExampleRef.
 func (value *ExampleRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -97,6 +107,11 @@ type HeaderRef struct {
 
 var _ jsonpointer.JSONPointable = (*HeaderRef)(nil)
 
+// MarshalYAML returns the YAML encoding of HeaderRef.
+func (value *HeaderRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of HeaderRef.
 func (value *HeaderRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -132,6 +147,11 @@ type LinkRef struct {
 	Value *Link
 }
 
+// MarshalYAML returns the YAML encoding of LinkRef.
+func (value *LinkRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of LinkRef.
 func (value *LinkRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -159,6 +179,11 @@ type ParameterRef struct {
 
 var _ jsonpointer.JSONPointable = (*ParameterRef)(nil)
 
+// MarshalYAML returns the YAML encoding of ParameterRef.
+func (value *ParameterRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of ParameterRef.
 func (value *ParameterRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -196,6 +221,11 @@ type ResponseRef struct {
 
 var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
 
+// MarshalYAML returns the YAML encoding of ResponseRef.
+func (value *ResponseRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of ResponseRef.
 func (value *ResponseRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -233,6 +263,11 @@ type RequestBodyRef struct {
 
 var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
 
+// MarshalYAML returns the YAML encoding of RequestBodyRef.
+func (value *RequestBodyRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of RequestBodyRef.
 func (value *RequestBodyRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -277,6 +312,11 @@ func NewSchemaRef(ref string, value *Schema) *SchemaRef {
 	}
 }
 
+// MarshalYAML returns the YAML encoding of SchemaRef.
+func (value *SchemaRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of SchemaRef.
 func (value *SchemaRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -314,6 +354,11 @@ type SecuritySchemeRef struct {
 
 var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil)
 
+// MarshalYAML returns the YAML encoding of SecuritySchemeRef.
+func (value *SecuritySchemeRef) MarshalYAML() (interface{}, error) {
+	return marshalRefYAML(value.Ref, value.Value)
+}
+
 // MarshalJSON returns the JSON encoding of SecuritySchemeRef.
 func (value *SecuritySchemeRef) MarshalJSON() ([]byte, error) {
 	return jsoninfo.MarshalRef(value.Ref, value.Value)
@@ -341,3 +386,11 @@ func (value SecuritySchemeRef) JSONLookup(token string) (interface{}, error) {
 	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
 	return ptr, err
 }
+
+// marshalRefYAML returns the YAML encoding of ref values.
+func marshalRefYAML(value string, otherwise interface{}) (interface{}, error) {
+	if value != "" {
+		return &Ref{Ref: value}, nil
+	}
+	return otherwise, nil
+}
diff --git a/openapi3/request_body.go b/openapi3/request_body.go
index c97563a..3e7c0d6 100644
--- a/openapi3/request_body.go
+++ b/openapi3/request_body.go
@@ -30,7 +30,7 @@ func (r RequestBodies) JSONLookup(token string) (interface{}, error) {
 // RequestBody is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#requestBodyObject
 type RequestBody struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Description string  `json:"description,omitempty" yaml:"description,omitempty"`
 	Required    bool    `json:"required,omitempty" yaml:"required,omitempty"`
@@ -109,5 +109,10 @@ func (requestBody *RequestBody) Validate(ctx context.Context) error {
 	if requestBody.Content == nil {
 		return errors.New("content of the request body is required")
 	}
+
+	if vo := getValidationOptions(ctx); !vo.ExamplesValidationDisabled {
+		vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false
+	}
+
 	return requestBody.Content.Validate(ctx)
 }
diff --git a/openapi3/response.go b/openapi3/response.go
index 62361ad..287e290 100644
--- a/openapi3/response.go
+++ b/openapi3/response.go
@@ -68,7 +68,7 @@ func (responses Responses) JSONLookup(token string) (interface{}, error) {
 // Response is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responseObject
 type Response struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Description *string `json:"description,omitempty" yaml:"description,omitempty"`
 	Headers     Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@@ -115,6 +115,9 @@ func (response *Response) Validate(ctx context.Context) error {
 	if response.Description == nil {
 		return errors.New("a short description of the response is required")
 	}
+	if vo := getValidationOptions(ctx); !vo.ExamplesValidationDisabled {
+		vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true
+	}
 
 	if content := response.Content; content != nil {
 		if err := content.Validate(ctx); err != nil {
diff --git a/openapi3/schema.go b/openapi3/schema.go
index b38d257..ba846d0 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -113,7 +113,7 @@ func (s SchemaRefs) JSONLookup(token string) (interface{}, error) {
 // Schema is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaObject
 type Schema struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	OneOf        SchemaRefs    `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
 	AnyOf        SchemaRefs    `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
@@ -769,7 +769,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 	}
 
 	if x := schema.Example; x != nil && !validationOpts.ExamplesValidationDisabled {
-		if err := validateExampleValue(x, schema); err != nil {
+		if err := validateExampleValue(ctx, x, schema); err != nil {
 			return fmt.Errorf("invalid example: %w", err)
 		}
 	}
@@ -1449,6 +1449,8 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 		return schema.expectedType(settings, TypeObject)
 	}
 
+	var me MultiError
+
 	if settings.asreq || settings.asrep {
 		properties := make([]string, 0, len(schema.Properties))
 		for propName := range schema.Properties {
@@ -1457,19 +1459,28 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 		sort.Strings(properties)
 		for _, propName := range properties {
 			propSchema := schema.Properties[propName]
+			reqRO := settings.asreq && propSchema.Value.ReadOnly
+			repWO := settings.asrep && propSchema.Value.WriteOnly
+
 			if value[propName] == nil {
-				if dlft := propSchema.Value.Default; dlft != nil {
+				if dlft := propSchema.Value.Default; dlft != nil && !reqRO && !repWO {
 					value[propName] = dlft
 					if f := settings.defaultsSet; f != nil {
 						settings.onceSettingDefaults.Do(f)
 					}
 				}
 			}
+
+			if value[propName] != nil {
+				if reqRO {
+					me = append(me, fmt.Errorf("readOnly property %q in request", propName))
+				} else if repWO {
+					me = append(me, fmt.Errorf("writeOnly property %q in response", propName))
+				}
+			}
 		}
 	}
 
-	var me MultiError
-
 	// "properties"
 	properties := schema.Properties
 	lenValue := int64(len(value))
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index 790b21a..3797389 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -30,7 +30,7 @@ var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
 // SecurityScheme is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securitySchemeObject
 type SecurityScheme struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Type             string      `json:"type,omitempty" yaml:"type,omitempty"`
 	Description      string      `json:"description,omitempty" yaml:"description,omitempty"`
@@ -176,7 +176,7 @@ func (ss *SecurityScheme) Validate(ctx context.Context) error {
 // OAuthFlows is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowsObject
 type OAuthFlows struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Implicit          *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
 	Password          *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
@@ -223,7 +223,7 @@ func (flows *OAuthFlows) Validate(ctx context.Context) error {
 // OAuthFlow is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowObject
 type OAuthFlow struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	AuthorizationURL string            `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
 	TokenURL         string            `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
diff --git a/openapi3/server.go b/openapi3/server.go
index 3a1a7ce..3f989d8 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -50,7 +50,7 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
 // Server is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#serverObject
 type Server struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	URL         string                     `json:"url" yaml:"url"`
 	Description string                     `json:"description,omitempty" yaml:"description,omitempty"`
@@ -197,7 +197,7 @@ func (server *Server) Validate(ctx context.Context) (err error) {
 // ServerVariable is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
 type ServerVariable struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Enum        []string `json:"enum,omitempty" yaml:"enum,omitempty"`
 	Default     string   `json:"default,omitempty" yaml:"default,omitempty"`
diff --git a/openapi3/tag.go b/openapi3/tag.go
index 8fb5ac3..b6c24c8 100644
--- a/openapi3/tag.go
+++ b/openapi3/tag.go
@@ -32,7 +32,7 @@ func (tags Tags) Validate(ctx context.Context) error {
 // Tag is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tagObject
 type Tag struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Name         string        `json:"name,omitempty" yaml:"name,omitempty"`
 	Description  string        `json:"description,omitempty" yaml:"description,omitempty"`
diff --git a/openapi3/validation_options.go b/openapi3/validation_options.go
index 5c0d01d..d890087 100644
--- a/openapi3/validation_options.go
+++ b/openapi3/validation_options.go
@@ -5,11 +5,12 @@ import "context"
 // ValidationOption allows the modification of how the OpenAPI document is validated.
 type ValidationOption func(options *ValidationOptions)
 
-// ValidationOptions provide configuration for validating OpenAPI documents.
+// ValidationOptions provides configuration for validating OpenAPI documents.
 type ValidationOptions struct {
-	SchemaFormatValidationEnabled   bool
-	SchemaPatternValidationDisabled bool
-	ExamplesValidationDisabled      bool
+	SchemaFormatValidationEnabled                    bool
+	SchemaPatternValidationDisabled                  bool
+	ExamplesValidationDisabled                       bool
+	examplesValidationAsReq, examplesValidationAsRes bool
 }
 
 type validationOptionsKey struct{}
diff --git a/openapi3/xml.go b/openapi3/xml.go
index 03686ad..f1ab96b 100644
--- a/openapi3/xml.go
+++ b/openapi3/xml.go
@@ -9,7 +9,7 @@ import (
 // XML is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xmlObject
 type XML struct {
-	ExtensionProps
+	ExtensionProps `json:"-" yaml:"-"`
 
 	Name      string `json:"name,omitempty" yaml:"name,omitempty"`
 	Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index f19123e..ffb7a1f 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -121,7 +121,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 	}
 
 	opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here
-	opts = append(opts, openapi3.VisitAsRequest())
+	opts = append(opts, openapi3.VisitAsResponse())
 	if options.MultiError {
 		opts = append(opts, openapi3.MultiErrors())
 	}


Test patch:
diff --git a/openapi3/example_validation_test.go b/openapi3/example_validation_test.go
index f6a495a..79288c2 100644
--- a/openapi3/example_validation_test.go
+++ b/openapi3/example_validation_test.go
@@ -9,13 +9,16 @@ import (
 
 func TestExamplesSchemaValidation(t *testing.T) {
 	type testCase struct {
-		name                    string
-		requestSchemaExample    string
-		responseSchemaExample   string
-		mediaTypeRequestExample string
-		parametersExample       string
-		componentExamples       string
-		errContains             string
+		name                                  string
+		requestSchemaExample                  string
+		responseSchemaExample                 string
+		mediaTypeRequestExample               string
+		mediaTypeResponseExample              string
+		readWriteOnlyMediaTypeRequestExample  string
+		readWriteOnlyMediaTypeResponseExample string
+		parametersExample                     string
+		componentExamples                     string
+		errContains                           string
 	}
 
 	testCases := []testCase{
@@ -26,7 +29,7 @@ func TestExamplesSchemaValidation(t *testing.T) {
             param1example:
               value: abcd
    `,
-			errContains: "invalid paths: invalid path /user: invalid operation POST: param1example",
+			errContains: `invalid paths: invalid path /user: invalid operation POST: param1example`,
 		},
 		{
 			name: "valid_parameter_examples",
@@ -41,7 +44,7 @@ func TestExamplesSchemaValidation(t *testing.T) {
 			parametersExample: `
           example: abcd
    `,
-			errContains: "invalid path /user: invalid operation POST: invalid example",
+			errContains: `invalid path /user: invalid operation POST: invalid example`,
 		},
 		{
 			name: "valid_parameter_example",
@@ -64,7 +67,7 @@ func TestExamplesSchemaValidation(t *testing.T) {
         email: bad
         password: short
    `,
-			errContains: "invalid paths: invalid path /user: invalid operation POST: example BadUser",
+			errContains: `invalid paths: invalid path /user: invalid operation POST: example BadUser`,
 		},
 		{
 			name: "valid_component_examples",
@@ -90,7 +93,7 @@ func TestExamplesSchemaValidation(t *testing.T) {
               email: bad
               password: short
    `,
-			errContains: "invalid path /user: invalid operation POST: invalid example",
+			errContains: `invalid path /user: invalid operation POST: invalid example`,
 		},
 		{
 			name: "valid_mediatype_examples",
@@ -109,7 +112,7 @@ func TestExamplesSchemaValidation(t *testing.T) {
         email: good@email.com
         # missing password
    `,
-			errContains: "schema \"CreateUserRequest\": invalid example",
+			errContains: `schema "CreateUserRequest": invalid example`,
 		},
 		{
 			name: "valid_schema_request_example",
@@ -127,7 +130,7 @@ func TestExamplesSchemaValidation(t *testing.T) {
         user_id: 1
         # missing access_token
    `,
-			errContains: "schema \"CreateUserResponse\": invalid example",
+			errContains: `schema "CreateUserResponse": invalid example`,
 		},
 		{
 			name: "valid_schema_response_example",
@@ -135,7 +138,64 @@ func TestExamplesSchemaValidation(t *testing.T) {
       example:
         user_id: 1
         access_token: "abcd"
-   `,
+  `,
+		},
+		{
+			name: "valid_readonly_writeonly_examples",
+			readWriteOnlyMediaTypeRequestExample: `
+            examples:
+              ReadWriteOnlyRequest:
+                $ref: '#/components/examples/ReadWriteOnlyRequestData'
+`,
+			readWriteOnlyMediaTypeResponseExample: `
+              examples:
+                ReadWriteOnlyResponse:
+                  $ref: '#/components/examples/ReadWriteOnlyResponseData'
+`,
+			componentExamples: `
+  examples:
+    ReadWriteOnlyRequestData:
+      value:
+        username: user
+        password: password
+    ReadWriteOnlyResponseData:
+      value:
+        user_id: 4321
+  `,
+		},
+		{
+			name: "invalid_readonly_request_examples",
+			readWriteOnlyMediaTypeRequestExample: `
+            examples:
+              ReadWriteOnlyRequest:
+                $ref: '#/components/examples/ReadWriteOnlyRequestData'
+`,
+			componentExamples: `
+  examples:
+    ReadWriteOnlyRequestData:
+      value:
+        username: user
+        password: password
+        user_id: 4321
+`,
+			errContains: `ReadWriteOnlyRequest: readOnly property "user_id" in request`,
+		},
+		{
+			name: "invalid_writeonly_response_examples",
+			readWriteOnlyMediaTypeResponseExample: `
+              examples:
+                ReadWriteOnlyResponse:
+                  $ref: '#/components/examples/ReadWriteOnlyResponseData'
+`,
+			componentExamples: `
+  examples:
+    ReadWriteOnlyResponseData:
+      value:
+        password: password
+        user_id: 4321
+`,
+
+			errContains: `ReadWriteOnlyResponse: writeOnly property "password" in response`,
 		},
 	}
 
@@ -198,7 +258,28 @@ paths:
           content:
             application/json:
               schema:
-                $ref: "#/components/schemas/CreateUserResponse"
+                $ref: "#/components/schemas/CreateUserResponse"`)
+					spec.WriteString(tc.mediaTypeResponseExample)
+					spec.WriteString(`
+  /readWriteOnly:
+    post:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/ReadWriteOnlyData"
+            required: true`)
+					spec.WriteString(tc.readWriteOnlyMediaTypeRequestExample)
+					spec.WriteString(`
+      responses:
+        '201':
+          description: a response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/ReadWriteOnlyData"`)
+					spec.WriteString(tc.readWriteOnlyMediaTypeResponseExample)
+					spec.WriteString(`
 components:
   schemas:
     CreateUserRequest:`)
@@ -223,7 +304,6 @@ components:
     CreateUserResponse:`)
 					spec.WriteString(tc.responseSchemaExample)
 					spec.WriteString(`
-      description: represents the response to a User creation
       required:
         - access_token
         - user_id
@@ -234,6 +314,28 @@ components:
           format: int64
           type: integer
       type: object
+    ReadWriteOnlyData:
+      required:
+        # only required in request
+        - username
+        - password
+        # only required in response
+        - user_id
+      properties:
+        username:
+          type: string
+          default: default
+          writeOnly: true # only sent in a request
+        password:
+          type: string
+          default: default
+          writeOnly: true # only sent in a request
+        user_id:
+          format: int64
+          default: 1
+          type: integer
+          readOnly: true # only returned in a response
+      type: object
 `)
 					spec.WriteString(tc.componentExamples)
 
@@ -278,7 +380,7 @@ func TestExampleObjectValidation(t *testing.T) {
               email: real@email.com
               password: validpassword
 `,
-			errContains: "invalid path /user: invalid operation POST: example and examples are mutually exclusive",
+			errContains: `invalid path /user: invalid operation POST: example and examples are mutually exclusive`,
 			componentExamples: `
   examples:
     BadUser:
@@ -295,7 +397,7 @@ func TestExampleObjectValidation(t *testing.T) {
     BadUser:
       description: empty user example
 `,
-			errContains: "invalid components: example \"BadUser\": no value or externalValue field",
+			errContains: `invalid components: example "BadUser": no value or externalValue field`,
 		},
 		{
 			name: "value_externalValue_mutual_exclusion",
@@ -308,7 +410,7 @@ func TestExampleObjectValidation(t *testing.T) {
         password: validpassword
       externalValue: 'http://example.com/examples/example'
 `,
-			errContains: "invalid components: example \"BadUser\": value and externalValue are mutually exclusive",
+			errContains: `invalid components: example "BadUser": value and externalValue are mutually exclusive`,
 		},
 	}
 
diff --git a/openapi3/issue241_test.go b/openapi3/issue241_test.go
new file mode 100644
index 0000000..14caa9b
--- /dev/null
+++ b/openapi3/issue241_test.go
@@ -0,0 +1,29 @@
+package openapi3_test
+
+import (
+	"bytes"
+	"io/ioutil"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func TestIssue241(t *testing.T) {
+	data, err := ioutil.ReadFile("testdata/issue241.yml")
+	require.NoError(t, err)
+
+	loader := openapi3.NewLoader()
+	loader.IsExternalRefsAllowed = true
+	spec, err := loader.LoadFromData(data)
+	require.NoError(t, err)
+
+	var buf bytes.Buffer
+	enc := yaml.NewEncoder(&buf)
+	enc.SetIndent(2)
+	err = enc.Encode(spec)
+	require.NoError(t, err)
+	require.Equal(t, string(data), buf.String())
+}
diff --git a/openapi3/testdata/issue241.yml b/openapi3/testdata/issue241.yml
new file mode 100644
index 0000000..07609c1
--- /dev/null
+++ b/openapi3/testdata/issue241.yml
@@ -0,0 +1,15 @@
+openapi: 3.0.3
+components:
+  schemas:
+    FooBar:
+      type: object
+      properties:
+        type_url:
+          type: string
+        value:
+          type: string
+          format: byte
+info:
+  title: sample
+  version: version not set
+paths: {}
diff --git a/openapi3filter/validate_readonly_test.go b/openapi3filter/validate_readonly_test.go
index 1152ec8..bad6c96 100644
--- a/openapi3filter/validate_readonly_test.go
+++ b/openapi3filter/validate_readonly_test.go
@@ -2,8 +2,9 @@ package openapi3filter
 
 import (
 	"bytes"
-	"encoding/json"
+	"io"
 	"net/http"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -12,82 +13,218 @@ import (
 	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
 )
 
-func TestValidatingRequestBodyWithReadOnlyProperty(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",
-                    "description": "Unique identifier for this object.",
-                    "pattern": "[0-9a-v]+$",
-                    "minLength": 20,
-                    "maxLength": 20,
-                    "readOnly": true
-                  }
-                }
-              }
-            }
-          }
-        },
-        "responses": {
-          "201": {
-            "description": "Successfully created a new account"
-          },
-          "400": {
-            "description": "The server could not understand the request due to invalid syntax",
-          }
-        }
-      }
-    }
-  }
-}
-`
+func TestReadOnlyWriteOnlyPropertiesValidation(t *testing.T) {
+	type testCase struct {
+		name                string
+		requestSchema       string
+		responseSchema      string
+		requestBody         string
+		responseBody        string
+		responseErrContains string
+		requestErrContains  string
+	}
 
-	type Request struct {
-		ID string `json:"_id"`
+	testCases := []testCase{
+		{
+			name: "valid_readonly_in_response_and_valid_writeonly_in_request",
+			requestSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["_id"],
+				"properties": {
+					"_id": {
+						"type": "string",
+						"writeOnly": true
+					}
+				}
+			}`,
+			responseSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["access_token"],
+				"properties": {
+					"access_token": {
+						"type": "string",
+						"readOnly": true
+					}
+				}
+			}`,
+			requestBody:  `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
+			responseBody: `{"access_token": "abcd"}`,
+		},
+		{
+			name: "valid_readonly_in_response_and_invalid_readonly_in_request",
+			requestSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["_id"],
+				"properties": {
+					"_id": {
+						"type": "string",
+						"readOnly": true
+					}
+				}
+			}`,
+			responseSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["access_token"],
+				"properties": {
+					"access_token": {
+						"type": "string",
+						"readOnly": true
+					}
+				}
+			}`,
+			requestBody:        `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
+			responseBody:       `{"access_token": "abcd"}`,
+			requestErrContains: `readOnly property "_id" in request`,
+		},
+		{
+			name: "invalid_writeonly_in_response_and_valid_writeonly_in_request",
+			requestSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["_id"],
+				"properties": {
+					"_id": {
+						"type": "string",
+						"writeOnly": true
+					}
+				}
+			}`,
+			responseSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["access_token"],
+				"properties": {
+					"access_token": {
+						"type": "string",
+						"writeOnly": true
+					}
+				}
+			}`,
+			requestBody:         `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
+			responseBody:        `{"access_token": "abcd"}`,
+			responseErrContains: `writeOnly property "access_token" in response`,
+		},
+		{
+			name: "invalid_writeonly_in_response_and_invalid_readonly_in_request",
+			requestSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["_id"],
+				"properties": {
+					"_id": {
+						"type": "string",
+						"readOnly": true
+					}
+				}
+			}`,
+			responseSchema: `
+			"schema":{
+				"type": "object",
+				"required": ["access_token"],
+				"properties": {
+					"access_token": {
+						"type": "string",
+						"writeOnly": true
+					}
+				}
+			}`,
+			requestBody:         `{"_id": "bt6kdc3d0cvp6u8u3ft0"}`,
+			responseBody:        `{"access_token": "abcd"}`,
+			responseErrContains: `writeOnly property "access_token" in response`,
+			requestErrContains:  `readOnly property "_id" in request`,
+		},
 	}
 
-	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)
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			spec := bytes.Buffer{}
+			spec.WriteString(`{
+				"openapi": "3.0.3",
+				"info": {
+					"version": "1.0.0",
+					"title": "title"
+				},
+				"paths": {
+					"/accounts": {
+						"post": {
+							"description": "Create a new account",
+							"requestBody": {
+								"required": true,
+								"content": {
+									"application/json": {`)
+			spec.WriteString(tc.requestSchema)
+			spec.WriteString(`}
+								}
+							},
+							"responses": {
+								"201": {
+									"description": "Successfully created a new account",
+									"content": {
+										"application/json": {`)
+			spec.WriteString(tc.responseSchema)
+			spec.WriteString(`}
+									}
+								},
+								"400": {
+									"description": "The server could not understand the request due to invalid syntax",
+								}
+							}
+						}
+					}
+				}
+				}`)
+
+			sl := openapi3.NewLoader()
+			doc, err := sl.LoadFromData(spec.Bytes())
+			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.MethodPost, "/accounts", strings.NewReader(tc.requestBody))
+			require.NoError(t, err)
+			httpReq.Header.Add(headerCT, "application/json")
 
-	b, err := json.Marshal(Request{ID: "bt6kdc3d0cvp6u8u3ft0"})
-	require.NoError(t, err)
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
 
-	httpReq, err := http.NewRequest(http.MethodPost, "/accounts", bytes.NewReader(b))
-	require.NoError(t, err)
-	httpReq.Header.Add(headerCT, "application/json")
+			reqValidationInput := &RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+			}
 
-	route, pathParams, err := router.FindRoute(httpReq)
-	require.NoError(t, err)
+			if tc.requestSchema != "" {
+				err = ValidateRequest(sl.Context, reqValidationInput)
 
-	err = ValidateRequest(sl.Context, &RequestValidationInput{
-		Request:    httpReq,
-		PathParams: pathParams,
-		Route:      route,
-	})
-	require.NoError(t, err)
+				if tc.requestErrContains != "" {
+					require.Error(t, err)
+					require.Contains(t, err.Error(), tc.requestErrContains)
+				} else {
+					require.NoError(t, err)
+				}
+			}
+
+			if tc.responseSchema != "" {
+				err = ValidateResponse(sl.Context, &ResponseValidationInput{
+					RequestValidationInput: reqValidationInput,
+					Status:                 201,
+					Header:                 httpReq.Header,
+					Body:                   io.NopCloser(strings.NewReader(tc.responseBody)),
+				})
+
+				if tc.responseErrContains != "" {
+					require.Error(t, err)
+					require.Contains(t, err.Error(), tc.responseErrContains)
+				} else {
+					require.NoError(t, err)
+				}
+			}
+		})
+	}
 }
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index b9151b8..c1d4630 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -372,15 +372,6 @@ func getValidationTests(t *testing.T) []*validationTest {
 				Title:  `property "name" is missing`,
 				Source: &ValidationErrorSource{Pointer: "/category/tags/0/name"}},
 		},
-		{
-			// TODO: Add support for validating readonly properties to upstream validator.
-			name: "error - readonly object attribute",
-			args: validationArgs{
-				r: newPetstoreRequest(t, http.MethodPost, "/pet",
-					bytes.NewBufferString(`{"id":213,"name":"Bahama","photoUrls":[]}}`)),
-			},
-			//wantErr: true,
-		},
 		{
 			name: "error - wrong attribute type",
 			args: validationArgs{