Repository: getkin/kin-openapi
Pull request #677 (2022-11-21 11:07:16)

fix: errors in oneOf not contain path (#676)
see #676 


Patch:
diff --git a/openapi3/schema.go b/openapi3/schema.go
index e5cded8..d2cd31c 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -861,10 +861,11 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
 		}
 	}
 	return &SchemaError{
-		Value:       value,
-		Schema:      schema,
-		SchemaField: "type",
-		Reason:      fmt.Sprintf("unhandled value of type %T", value),
+		Value:                 value,
+		Schema:                schema,
+		SchemaField:           "type",
+		Reason:                fmt.Sprintf("unhandled value of type %T", value),
+		customizeMessageError: settings.customizeMessageError,
 	}
 }
 
@@ -879,10 +880,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			return errSchema
 		}
 		return &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "enum",
-			Reason:      "value is not one of the allowed values",
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "enum",
+			Reason:                fmt.Sprintf("value %q is not one of the allowed values", value),
+			customizeMessageError: settings.customizeMessageError,
 		}
 	}
 
@@ -896,9 +898,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				return errSchema
 			}
 			return &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "not",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "not",
+				customizeMessageError: settings.customizeMessageError,
 			}
 		}
 	}
@@ -961,9 +964,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				return errSchema
 			}
 			e := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "oneOf",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "oneOf",
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if ok > 1 {
 				e.Origin = ErrOneOfConflict
@@ -1005,9 +1009,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				return errSchema
 			}
 			return &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "anyOf",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "anyOf",
+				customizeMessageError: settings.customizeMessageError,
 			}
 		}
 
@@ -1024,10 +1029,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 				return errSchema
 			}
 			return &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "allOf",
-				Origin:      err,
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "allOf",
+				Origin:                err,
+				customizeMessageError: settings.customizeMessageError,
 			}
 		}
 	}
@@ -1042,10 +1048,11 @@ func (schema *Schema) visitJSONNull(settings *schemaValidationSettings) (err err
 		return errSchema
 	}
 	return &SchemaError{
-		Value:       nil,
-		Schema:      schema,
-		SchemaField: "nullable",
-		Reason:      "Value is not nullable",
+		Value:                 nil,
+		Schema:                schema,
+		SchemaField:           "nullable",
+		Reason:                "Value is not nullable",
+		customizeMessageError: settings.customizeMessageError,
 	}
 }
 
@@ -1075,10 +1082,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "type",
-				Reason:      "Value must be an integer",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "type",
+				Reason:                fmt.Sprintf("value \"%g\" must be an integer", value),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1110,10 +1118,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "format",
-				Reason:      fmt.Sprintf("number must be an %s", schema.Format),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "format",
+				Reason:                fmt.Sprintf("number must be an %s", schema.Format),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1128,10 +1137,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "exclusiveMinimum",
-			Reason:      fmt.Sprintf("number must be more than %g", *schema.Min),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "exclusiveMinimum",
+			Reason:                fmt.Sprintf("number must be more than %g", *schema.Min),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1145,10 +1155,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "exclusiveMaximum",
-			Reason:      fmt.Sprintf("number must be less than %g", *schema.Max),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "exclusiveMaximum",
+			Reason:                fmt.Sprintf("number must be less than %g", *schema.Max),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1162,10 +1173,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "minimum",
-			Reason:      fmt.Sprintf("number must be at least %g", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "minimum",
+			Reason:                fmt.Sprintf("number must be at least %g", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1179,10 +1191,11 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "maximum",
-			Reason:      fmt.Sprintf("number must be at most %g", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "maximum",
+			Reason:                fmt.Sprintf("number must be at most %g", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1199,9 +1212,10 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "multipleOf",
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "multipleOf",
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1247,10 +1261,11 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "minLength",
-				Reason:      fmt.Sprintf("minimum string length is %d", minLength),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "minLength",
+				Reason:                fmt.Sprintf("minimum string length is %d", minLength),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1262,10 +1277,11 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := &SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "maxLength",
-				Reason:      fmt.Sprintf("maximum string length is %d", *maxLength),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "maxLength",
+				Reason:                fmt.Sprintf("maximum string length is %d", *maxLength),
+				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
 				return err
@@ -1286,10 +1302,11 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 	}
 	if cp := schema.compiledPattern; cp != nil && !cp.MatchString(value) {
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "pattern",
-			Reason:      fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "pattern",
+			Reason:                fmt.Sprintf(`string %q doesn't match the regular expression "%s"`, value, schema.Pattern),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1298,29 +1315,32 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 	}
 
 	// "format"
-	var formatErr string
+	var formatStrErr string
+	var formatErr error
 	if format := schema.Format; format != "" {
 		if f, ok := SchemaStringFormats[format]; ok {
 			switch {
 			case f.regexp != nil && f.callback == nil:
 				if cp := f.regexp; !cp.MatchString(value) {
-					formatErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String())
+					formatStrErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String())
 				}
 			case f.regexp == nil && f.callback != nil:
 				if err := f.callback(value); err != nil {
-					formatErr = err.Error()
+					formatErr = err
 				}
 			default:
-				formatErr = fmt.Sprintf("corrupted entry %q in SchemaStringFormats", format)
+				formatStrErr = fmt.Sprintf("corrupted entry %q in SchemaStringFormats", format)
 			}
 		}
 	}
-	if formatErr != "" {
+	if formatStrErr != "" || formatErr != nil {
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "format",
-			Reason:      formatErr,
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "format",
+			Reason:                formatStrErr,
+			Origin:                formatErr,
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1356,10 +1376,11 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "minItems",
-			Reason:      fmt.Sprintf("minimum number of items is %d", v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "minItems",
+			Reason:                fmt.Sprintf("minimum number of items is %d", v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1373,10 +1394,11 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "maxItems",
-			Reason:      fmt.Sprintf("maximum number of items is %d", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "maxItems",
+			Reason:                fmt.Sprintf("maximum number of items is %d", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1393,10 +1415,11 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "uniqueItems",
-			Reason:      "duplicate items found",
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "uniqueItems",
+			Reason:                "duplicate items found",
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1484,10 +1507,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "minProperties",
-			Reason:      fmt.Sprintf("there must be at least %d properties", v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "minProperties",
+			Reason:                fmt.Sprintf("there must be at least %d properties", v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1501,10 +1525,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "maxProperties",
-			Reason:      fmt.Sprintf("there must be at most %d properties", *v),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "maxProperties",
+			Reason:                fmt.Sprintf("there must be at most %d properties", *v),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1572,10 +1597,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			return errSchema
 		}
 		err := &SchemaError{
-			Value:       value,
-			Schema:      schema,
-			SchemaField: "properties",
-			Reason:      fmt.Sprintf("property %q is unsupported", k),
+			Value:                 value,
+			Schema:                schema,
+			SchemaField:           "properties",
+			Reason:                fmt.Sprintf("property %q is unsupported", k),
+			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
 			return err
@@ -1596,10 +1622,11 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 				return errSchema
 			}
 			err := markSchemaErrorKey(&SchemaError{
-				Value:       value,
-				Schema:      schema,
-				SchemaField: "required",
-				Reason:      fmt.Sprintf("property %q is missing", k),
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "required",
+				Reason:                fmt.Sprintf("property %q is missing", k),
+				customizeMessageError: settings.customizeMessageError,
 			}, k)
 			if !settings.multiError {
 				return err
@@ -1620,10 +1647,11 @@ func (schema *Schema) expectedType(settings *schemaValidationSettings, typ strin
 		return errSchema
 	}
 	return &SchemaError{
-		Value:       typ,
-		Schema:      schema,
-		SchemaField: "type",
-		Reason:      "Field must be set to " + schema.Type + " or not be present",
+		Value:                 typ,
+		Schema:                schema,
+		SchemaField:           "type",
+		Reason:                fmt.Sprintf("field must be set to %s or not be present", schema.Type),
+		customizeMessageError: settings.customizeMessageError,
 	}
 }
 
@@ -1639,17 +1667,24 @@ func (schema *Schema) compilePattern() (err error) {
 }
 
 type SchemaError struct {
-	Value       interface{}
-	reversePath []string
-	Schema      *Schema
-	SchemaField string
-	Reason      string
-	Origin      error
+	Value                 interface{}
+	reversePath           []string
+	Schema                *Schema
+	SchemaField           string
+	Reason                string
+	Origin                error
+	customizeMessageError func(err *SchemaError) string
 }
 
 var _ interface{ Unwrap() error } = SchemaError{}
 
 func markSchemaErrorKey(err error, key string) error {
+	var me multiErrorForOneOf
+
+	if errors.As(err, &me) {
+		err = me.Unwrap()
+	}
+
 	if v, ok := err.(*SchemaError); ok {
 		v.reversePath = append(v.reversePath, key)
 		return v
@@ -1664,17 +1699,7 @@ func markSchemaErrorKey(err error, key string) error {
 }
 
 func markSchemaErrorIndex(err error, index int) error {
-	if v, ok := err.(*SchemaError); ok {
-		v.reversePath = append(v.reversePath, strconv.FormatInt(int64(index), 10))
-		return v
-	}
-	if v, ok := err.(MultiError); ok {
-		for _, e := range v {
-			_ = markSchemaErrorIndex(e, index)
-		}
-		return v
-	}
-	return err
+	return markSchemaErrorKey(err, strconv.FormatInt(int64(index), 10))
 }
 
 func (err *SchemaError) JSONPointer() []string {
@@ -1687,11 +1712,14 @@ func (err *SchemaError) JSONPointer() []string {
 }
 
 func (err *SchemaError) Error() string {
-	if err.Origin != nil {
-		return err.Origin.Error()
+	if err.customizeMessageError != nil {
+		if msg := err.customizeMessageError(err); msg != "" {
+			return msg
+		}
 	}
 
 	buf := bytes.NewBuffer(make([]byte, 0, 256))
+
 	if len(err.reversePath) > 0 {
 		buf.WriteString(`Error at "`)
 		reversePath := err.reversePath
@@ -1701,6 +1729,13 @@ func (err *SchemaError) Error() string {
 		}
 		buf.WriteString(`": `)
 	}
+
+	if err.Origin != nil {
+		buf.WriteString(err.Origin.Error())
+
+		return buf.String()
+	}
+
 	reason := err.Reason
 	if reason == "" {
 		buf.WriteString(`Doesn't match schema "`)
@@ -1709,6 +1744,7 @@ func (err *SchemaError) Error() string {
 	} else {
 		buf.WriteString(reason)
 	}
+
 	if !SchemaErrorDetailsDisabled {
 		buf.WriteString("\nSchema:\n  ")
 		encoder := json.NewEncoder(buf)
@@ -1721,6 +1757,7 @@ func (err *SchemaError) Error() string {
 			panic(err)
 		}
 	}
+
 	return buf.String()
 }
 
diff --git a/openapi3/schema_validation_settings.go b/openapi3/schema_validation_settings.go
index 854ae84..5a28c8d 100644
--- a/openapi3/schema_validation_settings.go
+++ b/openapi3/schema_validation_settings.go
@@ -16,6 +16,8 @@ type schemaValidationSettings struct {
 
 	onceSettingDefaults sync.Once
 	defaultsSet         func()
+
+	customizeMessageError func(err *SchemaError) string
 }
 
 // FailFast returns schema validation errors quicker.
@@ -50,6 +52,12 @@ func DefaultsSet(f func()) SchemaValidationOption {
 	return func(s *schemaValidationSettings) { s.defaultsSet = f }
 }
 
+// SetSchemaErrorMessageCustomizer allows to override the schema error message.
+// If the passed function returns an empty string, it returns to the previous Error() implementation.
+func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption {
+	return func(s *schemaValidationSettings) { s.customizeMessageError = f }
+}
+
 func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings {
 	settings := &schemaValidationSettings{}
 	for _, opt := range opts {
diff --git a/openapi3filter/options.go b/openapi3filter/options.go
index 1622339..14c35d5 100644
--- a/openapi3filter/options.go
+++ b/openapi3filter/options.go
@@ -1,5 +1,7 @@
 package openapi3filter
 
+import "github.com/getkin/kin-openapi/openapi3"
+
 // DefaultOptions do not set an AuthenticationFunc.
 // A spec with security schemes defined will not pass validation
 // unless an AuthenticationFunc is defined.
@@ -21,4 +23,19 @@ type Options struct {
 
 	// See NoopAuthenticationFunc
 	AuthenticationFunc AuthenticationFunc
+
+	// Indicates whether default values are set in the
+	// request. If true, then they are not set
+	SkipSettingDefaults bool
+
+	customSchemaErrorFunc CustomSchemaErrorFunc
+}
+
+// CustomSchemaErrorFunc allows for custom the schema error message.
+type CustomSchemaErrorFunc func(err *openapi3.SchemaError) string
+
+// WithCustomSchemaErrorFunc sets a function to override the schema error message.
+// If the passed function returns an empty string, it returns to the previous Error() implementation.
+func (o *Options) WithCustomSchemaErrorFunc(f CustomSchemaErrorFunc) {
+	o.customSchemaErrorFunc = f
 }
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index ca81944..59c383d 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -334,6 +334,9 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 	case *pathParamDecoder:
 		_, found = vDecoder.pathParams[param]
 	case *urlValuesDecoder:
+		if schema.Value.Pattern != "" {
+			return dec.DecodePrimitive(param, sm, schema)
+		}
 		_, found = vDecoder.values[param]
 	case *headerParamDecoder:
 		_, found = vDecoder.header[param]
@@ -500,6 +503,10 @@ func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.Serializat
 		// HTTP request does not contain a value of the target query parameter.
 		return nil, ok, nil
 	}
+
+	if schema.Value.Type == "" && schema.Value.Pattern != "" {
+		return values[0], ok, nil
+	}
 	val, err := parsePrimitive(values[0], schema)
 	return val, ok, err
 }
@@ -526,10 +533,92 @@ func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationM
 		}
 		values = strings.Split(values[0], delim)
 	}
-	val, err := parseArray(values, schema)
+	val, err := d.parseArray(values, sm, schema)
 	return val, ok, err
 }
 
+// parseArray returns an array that contains items from a raw array.
+// Every item is parsed as a primitive value.
+// The function returns an error when an error happened while parse array's items.
+func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMethod, schemaRef *openapi3.SchemaRef) ([]interface{}, error) {
+	var value []interface{}
+
+	for i, v := range raw {
+		item, err := d.parseValue(v, schemaRef.Value.Items)
+		if err != nil {
+			if v, ok := err.(*ParseError); ok {
+				return nil, &ParseError{path: []interface{}{i}, Cause: v}
+			}
+			return nil, fmt.Errorf("item %d: %w", i, err)
+		}
+
+		// If the items are nil, then the array is nil. There shouldn't be case where some values are actual primitive
+		// values and some are nil values.
+		if item == nil {
+			return nil, nil
+		}
+		value = append(value, item)
+	}
+	return value, nil
+}
+
+func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (interface{}, error) {
+	if len(schema.Value.AllOf) > 0 {
+		var value interface{}
+		var err error
+		for _, sr := range schema.Value.AllOf {
+			value, err = d.parseValue(v, sr)
+			if value == nil || err != nil {
+				break
+			}
+		}
+		return value, err
+	}
+
+	if len(schema.Value.AnyOf) > 0 {
+		var value interface{}
+		var err error
+		for _, sr := range schema.Value.AnyOf {
+			value, err = d.parseValue(v, sr)
+			if err == nil {
+				return value, nil
+			}
+		}
+
+		return nil, err
+	}
+
+	if len(schema.Value.OneOf) > 0 {
+		isMatched := 0
+		var value interface{}
+		var err error
+		for _, sr := range schema.Value.OneOf {
+			result, err := d.parseValue(v, sr)
+			if err == nil {
+				value = result
+				isMatched++
+			}
+		}
+		if isMatched == 1 {
+			return value, nil
+		} else if isMatched > 1 {
+			return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
+		} else if isMatched == 0 {
+			return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
+		}
+
+		return nil, err
+	}
+
+	if schema.Value.Not != nil {
+		// TODO(decode not): handle decoding "not" JSON Schema
+		return nil, errors.New("not implemented: decoding 'not'")
+	}
+
+	return parsePrimitive(v, schema)
+
+}
+
 func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) {
 	var propsFn func(url.Values) (map[string]string, error)
 	switch sm.Style {
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index beb47aa..4acb9ff 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -178,6 +178,9 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param
 		opts = make([]openapi3.SchemaValidationOption, 0, 1)
 		opts = append(opts, openapi3.MultiErrors())
 	}
+	if options.customSchemaErrorFunc != nil {
+		opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
+	}
 	if err = schema.VisitJSON(value, opts...); err != nil {
 		return &RequestError{Input: input, Parameter: parameter, Err: err}
 	}
@@ -258,10 +261,15 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req
 	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.SkipSettingDefaults {
+		opts = append(opts, openapi3.DefaultsSet(func() { defaultsSet = true }))
+	}
 	if options.MultiError {
 		opts = append(opts, openapi3.MultiErrors())
 	}
+	if options.customSchemaErrorFunc != nil {
+		opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
+	}
 
 	// Validate JSON with the schema
 	if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index e90b5d6..abcbb4e 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -66,6 +66,9 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 	if options.MultiError {
 		opts = append(opts, openapi3.MultiErrors())
 	}
+	if options.customSchemaErrorFunc != nil {
+		opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc))
+	}
 
 	headers := make([]string, 0, len(response.Headers))
 	for k := range response.Headers {


Test patch:
diff --git a/openapi3/issue136_test.go b/openapi3/issue136_test.go
index 36c8b85..b5e9eeb 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: field must be set to string or not be present",
 		},
 	} {
 		t.Run(testcase.dflt, func(t *testing.T) {
diff --git a/openapi3/schema_formats_test.go b/openapi3/schema_formats_test.go
index 5cceb8c..3899fab 100644
--- a/openapi3/schema_formats_test.go
+++ b/openapi3/schema_formats_test.go
@@ -2,8 +2,10 @@ package openapi3
 
 import (
 	"context"
+	"errors"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -58,3 +60,48 @@ func TestIssue430(t *testing.T) {
 		}
 	}
 }
+
+func TestFormatCallback_WrapError(t *testing.T) {
+	var errSomething = errors.New("something error")
+
+	DefineStringFormatCallback("foobar", func(value string) error {
+		return errSomething
+	})
+
+	s := &Schema{Format: "foobar"}
+	err := s.VisitJSONString("blablabla")
+
+	assert.ErrorIs(t, err, errSomething)
+
+	delete(SchemaStringFormats, "foobar")
+}
+
+func TestReversePathInMessageSchemaError(t *testing.T) {
+	DefineIPv4Format()
+
+	SchemaErrorDetailsDisabled = true
+
+	const spc = `
+components:
+  schemas:
+    Something:
+      type: object
+      properties:
+        ip:
+          type: string
+          format: ipv4
+`
+	l := NewLoader()
+
+	doc, err := l.LoadFromData([]byte(spc))
+	require.NoError(t, err)
+
+	err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
+		`ip`: `123.0.0.11111`,
+	})
+
+	require.EqualError(t, err, `Error at "/ip": Not an IP address`)
+
+	delete(SchemaStringFormats, "ipv4")
+	SchemaErrorDetailsDisabled = false
+}
diff --git a/openapi3/schema_issue289_test.go b/openapi3/schema_issue289_test.go
index 6ab6b63..56d1d45 100644
--- a/openapi3/schema_issue289_test.go
+++ b/openapi3/schema_issue289_test.go
@@ -35,5 +35,5 @@ openapi: "3.0.1"
 		"name":    "kin-openapi",
 		"address": "127.0.0.1",
 	})
-	require.EqualError(t, err, ErrOneOfConflict.Error())
+	require.ErrorIs(t, err, ErrOneOfConflict)
 }
diff --git a/openapi3/schema_oneOf_test.go b/openapi3/schema_oneOf_test.go
index 7faf268..d3e689d 100644
--- a/openapi3/schema_oneOf_test.go
+++ b/openapi3/schema_oneOf_test.go
@@ -3,6 +3,7 @@ package openapi3
 import (
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -134,3 +135,50 @@ func TestVisitJSON_OneOf_BadDescriminatorType(t *testing.T) {
 	})
 	require.EqualError(t, err, "descriminator value is not a string")
 }
+
+func TestVisitJSON_OneOf_Path(t *testing.T) {
+	t.Parallel()
+
+	loader := NewLoader()
+	spc := `
+components:
+  schemas:
+    Something:
+      type: object
+      properties:
+        first:
+          type: object
+          properties:
+            second:
+              type: object
+              properties:
+                third:
+                  oneOf:
+                   - title: First rule
+                     type: string
+                     minLength: 5
+                     maxLength: 5
+                   - title: Second rule
+                     type: string
+                     minLength: 10
+                     maxLength: 10
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(spc))
+	require.NoError(t, err)
+
+	err = doc.Components.Schemas["Something"].Value.VisitJSON(map[string]interface{}{
+		"first": map[string]interface{}{
+			"second": map[string]interface{}{
+				"third": "123456789",
+			},
+		},
+	})
+
+	assert.Contains(t, err.Error(), `Error at "/first/second/third"`)
+
+	var sErr *SchemaError
+
+	assert.ErrorAs(t, err, &sErr)
+	assert.Equal(t, []string{"first", "second", "third"}, sErr.JSONPointer())
+}
diff --git a/openapi3/schema_validation_settings_test.go b/openapi3/schema_validation_settings_test.go
new file mode 100644
index 0000000..db52d3b
--- /dev/null
+++ b/openapi3/schema_validation_settings_test.go
@@ -0,0 +1,36 @@
+package openapi3_test
+
+import (
+	"fmt"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func ExampleSetSchemaErrorMessageCustomizer() {
+	loader := openapi3.NewLoader()
+	spc := `
+components:
+  schemas:
+    Something:
+      type: object
+      properties:
+        field:
+          title: Some field
+          type: string
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(spc))
+	if err != nil {
+		panic(err)
+	}
+
+	opt := openapi3.SetSchemaErrorMessageCustomizer(func(err *openapi3.SchemaError) string {
+		return fmt.Sprintf(`field "%s" should be string`, err.Schema.Title)
+	})
+
+	err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(123, opt)
+
+	fmt.Println(err.Error())
+
+	// Output: field "Some field" should be string
+}
diff --git a/openapi3filter/issue201_test.go b/openapi3filter/issue201_test.go
index 8db8620..8b2b99d 100644
--- a/openapi3filter/issue201_test.go
+++ b/openapi3filter/issue201_test.go
@@ -94,7 +94,7 @@ paths:
 		},
 
 		"invalid required header": {
-			err: `response header "X-Blup" doesn't match the schema: string doesn't match the regular expression "^blup$"`,
+			err: `response header "X-Blup" doesn't match the schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
 			headers: map[string]string{
 				"X-Blip": "blip",
 				"x-blop": "blop",
diff --git a/openapi3filter/issue625_test.go b/openapi3filter/issue625_test.go
new file mode 100644
index 0000000..d9e5bae
--- /dev/null
+++ b/openapi3filter/issue625_test.go
@@ -0,0 +1,123 @@
+package openapi3filter_test
+
+import (
+	"net/http"
+	"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/gorillamux"
+)
+
+func TestIssue625(t *testing.T) {
+
+	anyOfArraySpec := `
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: Sample API
+paths:
+ /items:
+  get:
+    description: Returns a list of stuff
+    parameters:
+    - description: test object
+      explode: false
+      in: query
+      name: test
+      required: false
+      schema:
+        type: array
+        items:
+         anyOf:
+          - type: integer
+          - type: boolean
+    responses:
+      '200':
+        description: Successful response
+`[1:]
+
+	oneOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "oneOf")
+
+	allOfArraySpec := strings.ReplaceAll(strings.ReplaceAll(anyOfArraySpec, "anyOf", "allOf"),
+		"type: boolean", "type: number")
+
+	tests := []struct {
+		name   string
+		spec   string
+		req    string
+		errStr string
+	}{
+		{
+			name: "success anyof object array",
+			spec: anyOfArraySpec,
+			req:  "/items?test=3,7",
+		},
+		{
+			name:   "failed anyof object array",
+			spec:   anyOfArraySpec,
+			req:    "/items?test=s1,s2",
+			errStr: `parameter "test" in query has an error: path 0: value s1: an invalid boolean: invalid syntax`,
+		},
+
+		{
+			name: "success allof object array",
+			spec: allOfArraySpec,
+			req:  `/items?test=1,3`,
+		},
+		{
+			name:   "failed allof object array",
+			spec:   allOfArraySpec,
+			req:    `/items?test=1.2,3.1`,
+			errStr: `parameter "test" in query has an error: Error at "/0": value "1.2" must be an integer`,
+		},
+		{
+			name: "success oneof object array",
+			spec: oneOfArraySpec,
+			req:  `/items?test=true,3`,
+		},
+		{
+			name:   "faled oneof object array",
+			spec:   oneOfArraySpec,
+			req:    `/items?test="val1","val2"`,
+			errStr: `parameter "test" in query has an error: item 0: decoding oneOf failed: 0 schemas matched`,
+		},
+	}
+
+	for _, testcase := range tests {
+		t.Run(testcase.name, func(t *testing.T) {
+			loader := openapi3.NewLoader()
+			ctx := loader.Context
+
+			doc, err := loader.LoadFromData([]byte(testcase.spec))
+			require.NoError(t, err)
+
+			err = doc.Validate(ctx)
+			require.NoError(t, err)
+
+			router, err := gorillamux.NewRouter(doc)
+			require.NoError(t, err)
+			httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
+			require.NoError(t, err)
+
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
+
+			requestValidationInput := &openapi3filter.RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+			}
+			err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
+			if testcase.errStr == "" {
+				require.NoError(t, err)
+			} else {
+				require.Contains(t, err.Error(), testcase.errStr)
+			}
+		},
+		)
+	}
+}
diff --git a/openapi3filter/issue639_test.go b/openapi3filter/issue639_test.go
new file mode 100644
index 0000000..2caf1bd
--- /dev/null
+++ b/openapi3filter/issue639_test.go
@@ -0,0 +1,100 @@
+package openapi3filter
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestIssue639(t *testing.T) {
+	loader := openapi3.NewLoader()
+	ctx := loader.Context
+	spec := `
+    openapi: 3.0.0
+    info:
+     version: 1.0.0
+     title: Sample API
+    paths:
+     /items:
+      put:
+        requestBody:
+         content:
+          application/json:
+            schema:
+              properties:
+                testWithdefault:
+                  default: false
+                  type: boolean
+                testNoDefault:
+                  type: boolean
+              type: object
+        responses:
+          '200':
+           description: Successful respons
+`[1:]
+
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	err = doc.Validate(ctx)
+	require.NoError(t, err)
+
+	router, err := gorillamux.NewRouter(doc)
+	require.NoError(t, err)
+
+	tests := []struct {
+		name               string
+		options            *Options
+		expectedDefaultVal interface{}
+	}{
+		{
+			name: "no defaults are added to requests",
+			options: &Options{
+				SkipSettingDefaults: true,
+			},
+			expectedDefaultVal: nil,
+		},
+
+		{
+			name:               "defaults are added to requests",
+			expectedDefaultVal: false,
+		},
+	}
+
+	for _, testcase := range tests {
+		t.Run(testcase.name, func(t *testing.T) {
+			body := "{\"testNoDefault\": true}"
+			httpReq, err := http.NewRequest(http.MethodPut, "/items", strings.NewReader(body))
+			require.NoError(t, err)
+			httpReq.Header.Set("Content-Type", "application/json")
+			require.NoError(t, err)
+
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
+
+			requestValidationInput := &RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+				Options:    testcase.options,
+			}
+			err = ValidateRequest(ctx, requestValidationInput)
+			require.NoError(t, err)
+			bodyAfterValidation, err := ioutil.ReadAll(httpReq.Body)
+			require.NoError(t, err)
+
+			raw := map[string]interface{}{}
+			err = json.Unmarshal(bodyAfterValidation, &raw)
+			require.NoError(t, err)
+			require.Equal(t, testcase.expectedDefaultVal,
+				raw["testWithdefault"], "default value must not be included")
+		})
+	}
+}
diff --git a/openapi3filter/issue641_test.go b/openapi3filter/issue641_test.go
new file mode 100644
index 0000000..3e0bba2
--- /dev/null
+++ b/openapi3filter/issue641_test.go
@@ -0,0 +1,109 @@
+package openapi3filter_test
+
+import (
+	"net/http"
+	"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/gorillamux"
+)
+
+func TestIssue641(t *testing.T) {
+
+	anyOfSpec := `
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: Sample API
+paths:
+ /items:
+  get:
+    description: Returns a list of stuff
+    parameters:
+    - description: test object
+      explode: false
+      in: query
+      name: test
+      required: false
+      schema:
+        anyOf:
+        - pattern: "^[0-9]{1,4}$"
+        - pattern: "^[0-9]{1,4}$"
+        type: string
+    responses:
+      '200':
+        description: Successful response
+`[1:]
+
+	allOfSpec := strings.ReplaceAll(anyOfSpec, "anyOf", "allOf")
+
+	tests := []struct {
+		name   string
+		spec   string
+		req    string
+		errStr string
+	}{
+
+		{
+			name: "success anyof pattern",
+			spec: anyOfSpec,
+			req:  "/items?test=51",
+		},
+		{
+			name:   "failed anyof pattern",
+			spec:   anyOfSpec,
+			req:    "/items?test=999999",
+			errStr: `parameter "test" in query has an error: Doesn't match schema "anyOf"`,
+		},
+
+		{
+			name: "success allof pattern",
+			spec: allOfSpec,
+			req:  `/items?test=51`,
+		},
+		{
+			name:   "failed allof pattern",
+			spec:   allOfSpec,
+			req:    `/items?test=999999`,
+			errStr: `parameter "test" in query has an error: string doesn't match the regular expression`,
+		},
+	}
+
+	for _, testcase := range tests {
+		t.Run(testcase.name, func(t *testing.T) {
+			loader := openapi3.NewLoader()
+			ctx := loader.Context
+
+			doc, err := loader.LoadFromData([]byte(testcase.spec))
+			require.NoError(t, err)
+
+			err = doc.Validate(ctx)
+			require.NoError(t, err)
+
+			router, err := gorillamux.NewRouter(doc)
+			require.NoError(t, err)
+			httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
+			require.NoError(t, err)
+
+			route, pathParams, err := router.FindRoute(httpReq)
+			require.NoError(t, err)
+
+			requestValidationInput := &openapi3filter.RequestValidationInput{
+				Request:    httpReq,
+				PathParams: pathParams,
+				Route:      route,
+			}
+			err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
+			if testcase.errStr == "" {
+				require.NoError(t, err)
+			} else {
+				require.Contains(t, err.Error(), testcase.errStr)
+			}
+		},
+		)
+	}
+}
diff --git a/openapi3filter/options_test.go b/openapi3filter/options_test.go
new file mode 100644
index 0000000..1273711
--- /dev/null
+++ b/openapi3filter/options_test.go
@@ -0,0 +1,83 @@
+package openapi3filter_test
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/openapi3filter"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func ExampleOptions_WithCustomSchemaErrorFunc() {
+	const spec = `
+openapi: 3.0.0
+info:
+  title: 'Validator'
+  version: 0.0.1
+paths:
+  /some:
+    post:
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                field:
+                  title: Some field
+                  type: integer
+      responses:
+        '200':
+          description: Created
+`
+
+	loader := openapi3.NewLoader()
+	doc, err := loader.LoadFromData([]byte(spec))
+	if err != nil {
+		panic(err)
+	}
+
+	err = doc.Validate(loader.Context)
+	if err != nil {
+		panic(err)
+	}
+
+	router, err := gorillamux.NewRouter(doc)
+	if err != nil {
+		panic(err)
+	}
+
+	opts := &openapi3filter.Options{}
+
+	opts.WithCustomSchemaErrorFunc(func(err *openapi3.SchemaError) string {
+		return fmt.Sprintf(`field "%s" must be an integer`, err.Schema.Title)
+	})
+
+	req, err := http.NewRequest(http.MethodPost, "/some", strings.NewReader(`{"field":"not integer"}`))
+	if err != nil {
+		panic(err)
+	}
+
+	req.Header.Add("Content-Type", "application/json")
+
+	route, pathParams, err := router.FindRoute(req)
+	if err != nil {
+		panic(err)
+	}
+
+	validationInput := &openapi3filter.RequestValidationInput{
+		Request:    req,
+		PathParams: pathParams,
+		Route:      route,
+		Options:    opts,
+	}
+	err = openapi3filter.ValidateRequest(context.Background(), validationInput)
+
+	fmt.Println(err.Error())
+
+	// Output: request body has an error: doesn't match the schema: field "Some field" must be an integer
+}
diff --git a/openapi3filter/unpack_errors_test.go b/openapi3filter/unpack_errors_test.go
index 552e20e..0ee48ad 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": field must be set to string or not be present
 	// Schema:
 	//   {
 	//     "example": "doggie",
@@ -93,7 +93,7 @@ func Example() {
 	//
 	// ===== Start New Error =====
 	// @body.status:
-	// 	Error at "/status": value is not one of the allowed values
+	// 	Error at "/status": value "invalidStatus" is not one of the allowed values
 	// Schema:
 	//   {
 	//     "description": "pet status in the store",
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index c1d4630..6fee135 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",
+			wantErrSchemaReason: "value \"available,sold\" is not one of the allowed values",
 			wantErrSchemaPath:   "/0",
 			wantErrSchemaValue:  "available,sold",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "value is not one of the allowed values",
+				Title: "value \"available,sold\" is not one of the allowed values",
 				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",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				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",
+			wantErrSchemaReason: "value \"fish,with,commas\" is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "fish,with,commas",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"fish,with,commas\" is not one of the allowed values",
 				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",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaPath:   "/",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				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 the schema",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaValue:  "watdis",
 			wantErrSchemaPath:   "/status",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				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 the schema",
-			wantErrSchemaReason: "Field must be set to array or not be present",
+			wantErrSchemaReason: "field must be set to array or not be present",
 			wantErrSchemaPath:   "/photoUrls",
 			wantErrSchemaValue:  "string",
 			// 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:  "field must be set to array or not be present",
 				Source: &ValidationErrorSource{Pointer: "/photoUrls"}},
 		},
 		{
@@ -659,7 +659,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][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
 	})
 }
 
@@ -701,6 +701,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][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
 	})
 }
diff --git a/routers/gorillamux/example_test.go b/routers/gorillamux/example_test.go
index 33ce98e..2ca3225 100644
--- a/routers/gorillamux/example_test.go
+++ b/routers/gorillamux/example_test.go
@@ -53,7 +53,7 @@ func Example() {
 	err = openapi3filter.ValidateResponse(ctx, responseValidationInput)
 	fmt.Println(err)
 	// Output:
-	// response body doesn't match the schema: Field must be set to string or not be present
+	// response body doesn't match the schema: field must be set to string or not be present
 	// Schema:
 	//   {
 	//     "type": "string"