Repository: getkin/kin-openapi
Pull request #748 (2023-01-21 22:15:34)

Refine schema error reason message
This PR adds tests to the behavior that was added in #735.
The purpose of the tests is to verify that the validated value doesn't appear in the `SchemaError.Reason` field.

Additionally, this PR refines the messages in `SchemaError.Reason` and makes sure `Reason` is always set.

ori-shalom: Thanks for the review.
I fixed your comments + rebased and uncommented the `additionalProperties` test.
As for the `readOnly` & `writeOnly` issue, I think we can address in another PR.

Patch:
diff --git a/openapi3/schema.go b/openapi3/schema.go
index fbf1900..0d1d183 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -784,6 +784,11 @@ func (schema *Schema) WithAnyAdditionalProperties() *Schema {
 	return schema
 }
 
+func (schema *Schema) WithoutAdditionalProperties() *Schema {
+	schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(false)}
+	return schema
+}
+
 func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema {
 	schema.AdditionalProperties = AdditionalProperties{}
 	if v != nil {
@@ -1134,11 +1139,12 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 		if settings.failfast {
 			return errSchema
 		}
+		allowedValues, _ := json.Marshal(enum)
 		return &SchemaError{
 			Value:                 value,
 			Schema:                schema,
 			SchemaField:           "enum",
-			Reason:                fmt.Sprintf("value is not one of the allowed values %q", schema.Enum),
+			Reason:                fmt.Sprintf("value is not one of the allowed values %s", string(allowedValues)),
 			customizeMessageError: settings.customizeMessageError,
 		}
 	}
@@ -1197,10 +1203,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 		}
 
 		var (
-			ok               = 0
-			validationErrors = multiErrorForOneOf{}
-			matchedOneOfIdx  = 0
-			tempValue        = value
+			ok                  = 0
+			validationErrors    = multiErrorForOneOf{}
+			matchedOneOfIndices = make([]int, 0)
+			tempValue           = value
 		)
 		for idx, item := range v {
 			v := item.Value
@@ -1222,14 +1228,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				continue
 			}
 
-			matchedOneOfIdx = idx
+			matchedOneOfIndices = append(matchedOneOfIndices, idx)
 			ok++
 		}
 
 		if ok != 1 {
-			if len(validationErrors) > 1 {
-				return fmt.Errorf("doesn't match schema due to: %w", validationErrors)
-			}
 			if settings.failfast {
 				return errSchema
 			}
@@ -1241,15 +1244,18 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			}
 			if ok > 1 {
 				e.Origin = ErrOneOfConflict
-			} else if len(validationErrors) == 1 {
-				e.Origin = validationErrors[0]
+				e.Reason = fmt.Sprintf(`value matches more than one schema from "oneOf" (matches schemas at indices %v)`, matchedOneOfIndices)
+			} else {
+				e.Origin = fmt.Errorf("doesn't match schema due to: %w", validationErrors)
+				e.Reason = `value doesn't match any schema from "oneOf"`
 			}
 
 			return e
 		}
 
+		// run again to inject default value that defined in matched oneOf schema
 		if settings.asreq || settings.asrep {
-			_ = v[matchedOneOfIdx].Value.visitJSON(settings, value)
+			_ = v[matchedOneOfIndices[0]].Value.visitJSON(settings, value)
 		}
 	}
 
@@ -1282,6 +1288,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				Value:                 value,
 				Schema:                schema,
 				SchemaField:           "anyOf",
+				Reason:                `doesn't match any schema from "anyOf"`,
 				customizeMessageError: settings.customizeMessageError,
 			}
 		}
@@ -1302,6 +1309,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				Value:                 value,
 				Schema:                schema,
 				SchemaField:           "allOf",
+				Reason:                `doesn't match all schemas from "allOf"`,
 				Origin:                err,
 				customizeMessageError: settings.customizeMessageError,
 			}
@@ -1337,7 +1345,7 @@ func (schema *Schema) VisitJSONBoolean(value bool) error {
 
 func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) {
 	if schemaType := schema.Type; schemaType != "" && schemaType != TypeBoolean {
-		return schema.expectedType(settings, TypeBoolean)
+		return schema.expectedType(settings, value)
 	}
 	return
 }
@@ -1368,7 +1376,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			me = append(me, err)
 		}
 	} else if schemaType != "" && schemaType != TypeNumber {
-		return schema.expectedType(settings, "number, integer")
+		return schema.expectedType(settings, value)
 	}
 
 	// formats
@@ -1489,6 +1497,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				Value:                 value,
 				Schema:                schema,
 				SchemaField:           "multipleOf",
+				Reason:                fmt.Sprintf("number must be a multiple of %g", *v),
 				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
@@ -1512,7 +1521,7 @@ func (schema *Schema) VisitJSONString(value string) error {
 
 func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error {
 	if schemaType := schema.Type; schemaType != "" && schemaType != TypeString {
-		return schema.expectedType(settings, TypeString)
+		return schema.expectedType(settings, value)
 	}
 
 	var me MultiError
@@ -1600,6 +1609,12 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 				}
 			case f.regexp == nil && f.callback != nil:
 				if err := f.callback(value); err != nil {
+					var schemaErr = &SchemaError{}
+					if errors.As(err, &schemaErr) {
+						formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%s)`, format, schemaErr.Reason)
+					} else {
+						formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%v)`, format, err)
+					}
 					formatErr = err
 				}
 			default:
@@ -1637,7 +1652,7 @@ func (schema *Schema) VisitJSONArray(value []interface{}) error {
 
 func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) error {
 	if schemaType := schema.Type; schemaType != "" && schemaType != TypeArray {
-		return schema.expectedType(settings, TypeArray)
+		return schema.expectedType(settings, value)
 	}
 
 	var me MultiError
@@ -1736,7 +1751,7 @@ func (schema *Schema) VisitJSONObject(value map[string]interface{}) error {
 
 func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]interface{}) error {
 	if schemaType := schema.Type; schemaType != "" && schemaType != TypeObject {
-		return schema.expectedType(settings, TypeObject)
+		return schema.expectedType(settings, value)
 	}
 
 	var me MultiError
@@ -1915,15 +1930,21 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 	return nil
 }
 
-func (schema *Schema) expectedType(settings *schemaValidationSettings, typ string) error {
+func (schema *Schema) expectedType(settings *schemaValidationSettings, value interface{}) error {
 	if settings.failfast {
 		return errSchema
 	}
+
+	a := "a"
+	switch schema.Type {
+	case TypeArray, TypeObject, TypeInteger:
+		a = "an"
+	}
 	return &SchemaError{
-		Value:                 typ,
+		Value:                 value,
 		Schema:                schema,
 		SchemaField:           "type",
-		Reason:                fmt.Sprintf("field must be set to %s or not be present", schema.Type),
+		Reason:                fmt.Sprintf("value must be %s %s", a, schema.Type),
 		customizeMessageError: settings.customizeMessageError,
 	}
 }
@@ -1933,21 +1954,29 @@ func (schema *Schema) compilePattern() (err error) {
 		return &SchemaError{
 			Schema:      schema,
 			SchemaField: "pattern",
+			Origin:      err,
 			Reason:      fmt.Sprintf("cannot compile pattern %q: %v", schema.Pattern, err),
 		}
 	}
 	return nil
 }
 
+// SchemaError is an error that occurs during schema validation.
 type SchemaError struct {
-	Value       interface{}
+	// Value is the value that failed validation.
+	Value interface{}
+	// reversePath is the path to the value that failed validation.
 	reversePath []string
-	Schema      *Schema
+	// Schema is the schema that failed validation.
+	Schema *Schema
+	// SchemaField is the field of the schema that failed validation.
 	SchemaField string
 	// Reason is a human-readable message describing the error.
 	// The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages.
-	Reason                string
-	Origin                error
+	Reason string
+	// Origin is the original error that caused this error.
+	Origin error
+	// customizeMessageError is a function that can be used to customize the error message.
 	customizeMessageError func(err *SchemaError) string
 }
 


Test patch:
diff --git a/openapi3/issue136_test.go b/openapi3/issue136_test.go
index b5e9eeb..3aa7edd 100644
--- a/openapi3/issue136_test.go
+++ b/openapi3/issue136_test.go
@@ -31,7 +31,7 @@ components:
 		},
 		{
 			dflt: `1`,
-			err:  "invalid components: invalid schema default: field must be set to string or not be present",
+			err:  "invalid components: invalid schema default: value must be a string",
 		},
 	} {
 		t.Run(testcase.dflt, func(t *testing.T) {
diff --git a/openapi3/issue735_test.go b/openapi3/issue735_test.go
new file mode 100644
index 0000000..f7e420c
--- /dev/null
+++ b/openapi3/issue735_test.go
@@ -0,0 +1,278 @@
+package openapi3
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+type testCase struct {
+	name             string
+	schema           *Schema
+	value            interface{}
+	extraNotContains []interface{}
+	options          []SchemaValidationOption
+}
+
+func TestIssue735(t *testing.T) {
+	DefineStringFormat("uuid", FormatOfStringForUUIDOfRFC4122)
+	DefineStringFormat("email", FormatOfStringForEmail)
+	DefineIPv4Format()
+	DefineIPv6Format()
+
+	testCases := []testCase{
+		{
+			name:   "type string",
+			schema: NewStringSchema(),
+			value:  42,
+		},
+		{
+			name:   "type boolean",
+			schema: NewBoolSchema(),
+			value:  42,
+		},
+		{
+			name:   "type integer",
+			schema: NewIntegerSchema(),
+			value:  "foo",
+		},
+		{
+			name:   "type number",
+			schema: NewFloat64Schema(),
+			value:  "foo",
+		},
+		{
+			name:   "type array",
+			schema: NewArraySchema(),
+			value:  42,
+		},
+		{
+			name:   "type object",
+			schema: NewObjectSchema(),
+			value:  42,
+		},
+		{
+			name:   "min",
+			schema: NewSchema().WithMin(100),
+			value:  42,
+		},
+		{
+			name:   "max",
+			schema: NewSchema().WithMax(0),
+			value:  42,
+		},
+		{
+			name:   "exclusive min",
+			schema: NewSchema().WithMin(100).WithExclusiveMin(true),
+			value:  42,
+		},
+		{
+			name:   "exclusive max",
+			schema: NewSchema().WithMax(0).WithExclusiveMax(true),
+			value:  42,
+		},
+		{
+			name:   "multiple of",
+			schema: &Schema{MultipleOf: Float64Ptr(5.0)},
+			value:  42,
+		},
+		{
+			name:   "enum",
+			schema: NewSchema().WithEnum(3, 5),
+			value:  42,
+		},
+		{
+			name:   "min length",
+			schema: NewSchema().WithMinLength(100),
+			value:  "foo",
+		},
+		{
+			name:   "max length",
+			schema: NewSchema().WithMaxLength(0),
+			value:  "foo",
+		},
+		{
+			name:   "pattern",
+			schema: NewSchema().WithPattern("[0-9]"),
+			value:  "foo",
+		},
+		{
+			name:             "items",
+			schema:           NewSchema().WithItems(NewStringSchema()),
+			value:            []interface{}{42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name:             "min items",
+			schema:           NewSchema().WithMinItems(100),
+			value:            []interface{}{42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name:             "max items",
+			schema:           NewSchema().WithMaxItems(0),
+			value:            []interface{}{42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name:             "unique items",
+			schema:           NewSchema().WithUniqueItems(true),
+			value:            []interface{}{42, 42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name:             "min properties",
+			schema:           NewSchema().WithMinProperties(100),
+			value:            map[string]interface{}{"foo": 42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name:             "max properties",
+			schema:           NewSchema().WithMaxProperties(0),
+			value:            map[string]interface{}{"foo": 42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name:             "additional properties other schema type",
+			schema:           NewSchema().WithAdditionalProperties(NewStringSchema()),
+			value:            map[string]interface{}{"foo": 42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name: "additional properties false",
+			schema: &Schema{AdditionalProperties: AdditionalProperties{
+				Has: BoolPtr(false),
+			}},
+			value:            map[string]interface{}{"foo": 42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name: "invalid properties schema",
+			schema: NewSchema().WithProperties(map[string]*Schema{
+				"foo": NewStringSchema(),
+			}),
+			value:            map[string]interface{}{"foo": 42},
+			extraNotContains: []interface{}{42},
+		},
+		// TODO: uncomment when https://github.com/getkin/kin-openapi/issues/502 is fixed
+		//{
+		//	name: "read only properties",
+		//	schema: NewSchema().WithProperties(map[string]*Schema{
+		//		"foo": {ReadOnly: true},
+		//	}).WithoutAdditionalProperties(),
+		//	value:            map[string]interface{}{"foo": 42},
+		//	extraNotContains: []interface{}{42},
+		//	options:          []SchemaValidationOption{VisitAsRequest()},
+		//},
+		//{
+		//	name: "write only properties",
+		//	schema: NewSchema().WithProperties(map[string]*Schema{
+		//		"foo": {WriteOnly: true},
+		//	}).WithoutAdditionalProperties(),
+		//	value:            map[string]interface{}{"foo": 42},
+		//	extraNotContains: []interface{}{42},
+		//	options:          []SchemaValidationOption{VisitAsResponse()},
+		//},
+		{
+			name: "required properties",
+			schema: &Schema{
+				Properties: Schemas{
+					"bar": NewStringSchema().NewRef(),
+				},
+				Required: []string{"bar"},
+			},
+			value:            map[string]interface{}{"foo": 42},
+			extraNotContains: []interface{}{42},
+		},
+		{
+			name: "one of (matches more then one)",
+			schema: NewOneOfSchema(
+				&Schema{MultipleOf: Float64Ptr(6)},
+				&Schema{MultipleOf: Float64Ptr(7)},
+			),
+			value: 42,
+		},
+		{
+			name: "one of (no matches)",
+			schema: NewOneOfSchema(
+				&Schema{MultipleOf: Float64Ptr(5)},
+				&Schema{MultipleOf: Float64Ptr(10)},
+			),
+			value: 42,
+		},
+		{
+			name: "any of",
+			schema: NewAnyOfSchema(
+				&Schema{MultipleOf: Float64Ptr(5)},
+				&Schema{MultipleOf: Float64Ptr(10)},
+			),
+			value: 42,
+		},
+		{
+			name: "all of (match some)",
+			schema: NewAllOfSchema(
+				&Schema{MultipleOf: Float64Ptr(6)},
+				&Schema{MultipleOf: Float64Ptr(5)},
+			),
+			value: 42,
+		},
+		{
+			name: "all of (no match)",
+			schema: NewAllOfSchema(
+				&Schema{MultipleOf: Float64Ptr(10)},
+				&Schema{MultipleOf: Float64Ptr(5)},
+			),
+			value: 42,
+		},
+		{
+			name:   "uuid format",
+			schema: NewUUIDSchema(),
+			value:  "foo",
+		},
+		{
+			name:   "date time format",
+			schema: NewDateTimeSchema(),
+			value:  "foo",
+		},
+		{
+			name:   "date format",
+			schema: NewSchema().WithFormat("date"),
+			value:  "foo",
+		},
+		{
+			name:   "ipv4 format",
+			schema: NewSchema().WithFormat("ipv4"),
+			value:  "foo",
+		},
+		{
+			name:   "ipv6 format",
+			schema: NewSchema().WithFormat("ipv6"),
+			value:  "foo",
+		},
+		{
+			name:   "email format",
+			schema: NewSchema().WithFormat("email"),
+			value:  "foo",
+		},
+		{
+			name:   "byte format",
+			schema: NewBytesSchema(),
+			value:  "foo!",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := tc.schema.VisitJSON(tc.value, tc.options...)
+			var schemaError = &SchemaError{}
+			require.Error(t, err)
+			require.ErrorAs(t, err, &schemaError)
+			require.NotZero(t, schemaError.Reason)
+			require.NotContains(t, schemaError.Reason, fmt.Sprint(tc.value))
+			for _, extra := range tc.extraNotContains {
+				require.NotContains(t, schemaError.Reason, fmt.Sprint(extra))
+			}
+		})
+	}
+}
diff --git a/openapi3/schema_oneOf_test.go b/openapi3/schema_oneOf_test.go
index 90a23cc..8d54519 100644
--- a/openapi3/schema_oneOf_test.go
+++ b/openapi3/schema_oneOf_test.go
@@ -106,7 +106,7 @@ func TestVisitJSON_OneOf_MissingField(t *testing.T) {
 		"name":  "snoopy",
 		"$type": "dog",
 	})
-	require.EqualError(t, err, "Error at \"/barks\": property \"barks\" is missing\nSchema:\n  {\n    \"properties\": {\n      \"$type\": {\n        \"enum\": [\n          \"dog\"\n        ],\n        \"type\": \"string\"\n      },\n      \"barks\": {\n        \"type\": \"boolean\"\n      },\n      \"name\": {\n        \"type\": \"string\"\n      }\n    },\n    \"required\": [\n      \"name\",\n      \"barks\",\n      \"$type\"\n    ],\n    \"type\": \"object\"\n  }\n\nValue:\n  {\n    \"$type\": \"dog\",\n    \"name\": \"snoopy\"\n  }\n")
+	require.EqualError(t, err, "doesn't match schema due to: Error at \"/barks\": property \"barks\" is missing\nSchema:\n  {\n    \"properties\": {\n      \"$type\": {\n        \"enum\": [\n          \"dog\"\n        ],\n        \"type\": \"string\"\n      },\n      \"barks\": {\n        \"type\": \"boolean\"\n      },\n      \"name\": {\n        \"type\": \"string\"\n      }\n    },\n    \"required\": [\n      \"name\",\n      \"barks\",\n      \"$type\"\n    ],\n    \"type\": \"object\"\n  }\n\nValue:\n  {\n    \"$type\": \"dog\",\n    \"name\": \"snoopy\"\n  }\n")
 }
 
 func TestVisitJSON_OneOf_NoDiscriptor_MissingField(t *testing.T) {
diff --git a/openapi3filter/issue641_test.go b/openapi3filter/issue641_test.go
index ee0be10..1c5277d 100644
--- a/openapi3filter/issue641_test.go
+++ b/openapi3filter/issue641_test.go
@@ -57,7 +57,7 @@ paths:
 			name:   "failed anyof pattern",
 			spec:   anyOfSpec,
 			req:    "/items?test=999999",
-			errStr: `parameter "test" in query has an error: Doesn't match schema "anyOf"`,
+			errStr: `parameter "test" in query has an error: doesn't match any schema from "anyOf"`,
 		},
 
 		{
diff --git a/openapi3filter/unpack_errors_test.go b/openapi3filter/unpack_errors_test.go
index 9fb7cfe..befff10 100644
--- a/openapi3filter/unpack_errors_test.go
+++ b/openapi3filter/unpack_errors_test.go
@@ -81,7 +81,7 @@ func Example() {
 	// Output:
 	// ===== Start New Error =====
 	// @body.name:
-	// 	Error at "/name": field must be set to string or not be present
+	// 	Error at "/name": value must be a string
 	// Schema:
 	//   {
 	//     "example": "doggie",
@@ -89,11 +89,11 @@ func Example() {
 	//   }
 	//
 	// Value:
-	//   "number, integer"
+	//   100
 	//
 	// ===== Start New Error =====
 	// @body.status:
-	// 	Error at "/status": value is not one of the allowed values ["available" "pending" "sold"]
+	// 	Error at "/status": value is not one of the allowed values ["available","pending","sold"]
 	// Schema:
 	//   {
 	//     "description": "pet status in the store",
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index bc27300..4652ada 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -244,11 +244,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
+			wantErrSchemaReason: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
 			wantErrSchemaPath:   "/0",
 			wantErrSchemaValue:  "available,sold",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
+				Title: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
 				Detail: "value available,sold at /0 must be one of: available, pending, sold; " +
 					// TODO: do we really want to use this heuristic to guess
 					//  that they're using the wrong serialization?
@@ -262,11 +262,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
+			wantErrSchemaReason: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
+				Title:  "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
 				Detail: "value watdis at /1 must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Parameter: "status"}},
 		},
@@ -278,11 +278,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "kind",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values [\"dog\" \"cat\" \"turtle\" \"bird,with,commas\"]",
+			wantErrSchemaReason: "value is not one of the allowed values [\"dog\",\"cat\",\"turtle\",\"bird,with,commas\"]",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "fish,with,commas",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values [\"dog\" \"cat\" \"turtle\" \"bird,with,commas\"]",
+				Title:  "value is not one of the allowed values [\"dog\",\"cat\",\"turtle\",\"bird,with,commas\"]",
 				Detail: "value fish,with,commas at /1 must be one of: dog, cat, turtle, bird,with,commas",
 				// No 'perhaps you intended' because its the right serialization format
 				Source: &ValidationErrorSource{Parameter: "kind"}},
@@ -304,11 +304,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "x-environment",
 			wantErrParamIn:      "header",
-			wantErrSchemaReason: "value is not one of the allowed values [\"demo\" \"prod\"]",
+			wantErrSchemaReason: "value is not one of the allowed values [\"demo\",\"prod\"]",
 			wantErrSchemaPath:   "/",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values [\"demo\" \"prod\"]",
+				Title:  "value is not one of the allowed values [\"demo\",\"prod\"]",
 				Detail: "value watdis at / must be one of: demo, prod",
 				Source: &ValidationErrorSource{Parameter: "x-environment"}},
 		},
@@ -323,11 +323,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
 			},
 			wantErrReason:       "doesn't match schema #/components/schemas/PetWithRequired",
-			wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
+			wantErrSchemaReason: "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
 			wantErrSchemaValue:  "watdis",
 			wantErrSchemaPath:   "/status",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
+				Title:  "value is not one of the allowed values [\"available\",\"pending\",\"sold\"]",
 				Detail: "value watdis at /status must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Pointer: "/status"}},
 		},
@@ -379,13 +379,13 @@ func getValidationTests(t *testing.T) []*validationTest {
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`)),
 			},
 			wantErrReason:       "doesn't match schema #/components/schemas/PetWithRequired",
-			wantErrSchemaReason: "field must be set to array or not be present",
+			wantErrSchemaReason: "value must be an array",
 			wantErrSchemaPath:   "/photoUrls",
-			wantErrSchemaValue:  "string",
+			wantErrSchemaValue:  "http://cat",
 			// TODO: this shouldn't say "or not be present", but this requires recursively resolving
 			//  innerErr.JSONPointer() against e.RequestBody.Content["application/json"].Schema.Value (.Required, .Properties)
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "field must be set to array or not be present",
+				Title:  "value must be an array",
 				Source: &ValidationErrorSource{Pointer: "/photoUrls"}},
 		},
 		{
@@ -396,6 +396,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 			wantErrReason:             "doesn't match schema",
 			wantErrSchemaPath:         "/",
 			wantErrSchemaValue:        map[string]string{"name": "Bahama"},
+			wantErrSchemaReason:       `doesn't match all schemas from "allOf"`,
 			wantErrSchemaOriginReason: `property "photoUrls" is missing`,
 			wantErrSchemaOriginValue:  map[string]string{"name": "Bahama"},
 			wantErrSchemaOriginPath:   "/photoUrls",
@@ -659,7 +660,7 @@ func TestValidationHandler_ServeHTTP(t *testing.T) {
 		body, err := ioutil.ReadAll(resp.Body)
 		require.NoError(t, err)
 		require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
-		require.Equal(t, "[422][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
+		require.Equal(t, "[422][][] value must be an array [source pointer=/photoUrls]", string(body))
 	})
 }
 
@@ -701,6 +702,6 @@ func TestValidationHandler_Middleware(t *testing.T) {
 		body, err := ioutil.ReadAll(resp.Body)
 		require.NoError(t, err)
 		require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
-		require.Equal(t, "[422][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
+		require.Equal(t, "[422][][] value must be an array [source pointer=/photoUrls]", string(body))
 	})
 }
diff --git a/routers/gorillamux/example_test.go b/routers/gorillamux/example_test.go
index 54058cd..9f949bd 100644
--- a/routers/gorillamux/example_test.go
+++ b/routers/gorillamux/example_test.go
@@ -53,12 +53,12 @@ func Example() {
 	err = openapi3filter.ValidateResponse(ctx, responseValidationInput)
 	fmt.Println(err)
 	// Output:
-	// response body doesn't match schema pathref.openapi.yml#/components/schemas/TestSchema: field must be set to string or not be present
+	// response body doesn't match schema pathref.openapi.yml#/components/schemas/TestSchema: value must be a string
 	// Schema:
 	//   {
 	//     "type": "string"
 	//   }
 	//
 	// Value:
-	//   "object"
+	//   {}
 }