Repository: getkin/kin-openapi
Pull request #325 (2021-03-18 16:18:26)

prepare for #210
* have error messages follow golint
* use %q instead of '%s'
* introduce some constants
* document some things
* rely less on verbiage


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 41b196f..d82af1e 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -14,29 +14,62 @@ jobs:
       matrix:
         # Locked at https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
         os:
-        - ubuntu-18.04
+        - ubuntu-20.04
         - windows-2019
+        - macos-10.15
     runs-on: ${{ matrix.os }}
+    defaults:
+      run:
+        shell: bash
     steps:
-    - uses: actions/checkout@v2
 
     - uses: actions/setup-go@v2
       with:
         go-version: 1.x
-    - run: go version
+
+    - id: go-cache-paths
+      run: |
+        echo "::set-output name=go-build::$(go env GOCACHE)"
+        echo "::set-output name=go-mod::$(go env GOMODCACHE)"
+
+    - name: Go Build Cache
+      uses: actions/cache@v2
+      with:
+        path: ${{ steps.go-cache-paths.outputs.go-build }}
+        key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
+
+    - name: Go Mod Cache
+      uses: actions/cache@v2
+      with:
+        path: ${{ steps.go-cache-paths.outputs.go-mod }}
+        key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
+
+
+    - uses: actions/checkout@v2
 
     - run: go mod download && go mod tidy && go mod verify
-    - run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
-      shell: bash
+    - if: runner.os == 'Linux'
+      run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
 
     - run: go vet ./...
-    - run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
-      shell: bash
+    - if: runner.os == 'Linux'
+      run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
 
     - run: go fmt ./...
-    - run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
-      shell: bash
+    - if: runner.os == 'Linux'
+      run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
 
     - run: go test ./...
-    - run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
-      shell: bash
+    - if: runner.os == 'Linux'
+      run: git --no-pager diff && [[ $(git --no-pager diff --name-only | wc -l) = 0 ]]
+
+
+    - if: runner.os == 'Linux'
+      name: Errors must not be capitalized https://github.com/golang/go/wiki/CodeReviewComments#error-strings
+      run: |
+        ! git grep -E '(fmt|errors)[^(]+\(.[A-Z]'
+
+    - if: runner.os == 'Linux'
+      name: Did you mean %q
+      run: |
+        ! git grep -E "'[%].'"
diff --git a/README.md b/README.md
index c1761a6..8d9484d 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ Here's some projects that depend on _kin-openapi_:
 
 # Some recipes
 ## Loading OpenAPI document
-Use `SwaggerLoader`, which resolves all JSON references:
+Use `SwaggerLoader`, which resolves all references:
 ```go
 swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("swagger.json")
 ```
@@ -108,9 +108,7 @@ func main() {
 	responseValidationInput := &openapi3filter.ResponseValidationInput{
 		RequestValidationInput: requestValidationInput,
 		Status:                 respStatus,
-		Header: http.Header{
-			"Content-Type": []string{respContentType},
-		},
+		Header:                 http.Header{"Content-Type": []string{respContentType}},
 	}
 	if respBody != nil {
 		data, _ := json.Marshal(respBody)
@@ -160,7 +158,7 @@ func xmlBodyDecoder(body []byte) (interface{}, error) {
 }
 ```
 
-## Custom function for check uniqueness of JSON array
+## Custom function to check uniqueness of array items
 
 By defaut, the library check unique items by below predefined function
 
@@ -169,8 +167,6 @@ func isSliceOfUniqueItems(xs []interface{}) bool {
 	s := len(xs)
 	m := make(map[string]struct{}, s)
 	for _, x := range xs {
-		// The input slice is coverted from a JSON string, there shall
-		// have no error when covert it back.
 		key, _ := json.Marshal(&x)
 		m[string(key)] = struct{}{}
 	}
@@ -180,7 +176,7 @@ func isSliceOfUniqueItems(xs []interface{}) bool {
 
 In the predefined function using `json.Marshal` to generate a string can
 be used as a map key which is to support check the uniqueness of an array
-when the array items are JSON objects or JSON arraies. You can register
+when the array items are objects or arrays. You can register
 you own function according to your input data to get better performance:
 
 ```go
@@ -194,7 +190,7 @@ func main() {
 }
 
 func arrayUniqueItemsChecker(items []interface{}) bool {
-	// Check the uniqueness of the input slice(array in JSON)
+	// Check the uniqueness of the input slice
 }
 ```
 
diff --git a/jsoninfo/marshal.go b/jsoninfo/marshal.go
index 93de99a..2a98d68 100644
--- a/jsoninfo/marshal.go
+++ b/jsoninfo/marshal.go
@@ -59,11 +59,11 @@ func (encoder *ObjectEncoder) EncodeStructFieldsAndExtensions(value interface{})
 	// Follow "encoding/json" semantics
 	if reflection.Kind() != reflect.Ptr {
 		// Panic because this is a clear programming error
-		panic(fmt.Errorf("Value %s is not a pointer", reflection.Type().String()))
+		panic(fmt.Errorf("value %s is not a pointer", reflection.Type().String()))
 	}
 	if reflection.IsNil() {
 		// Panic because this is a clear programming error
-		panic(fmt.Errorf("Value %s is nil", reflection.Type().String()))
+		panic(fmt.Errorf("value %s is nil", reflection.Type().String()))
 	}
 
 	// Take the element
@@ -146,7 +146,7 @@ iteration:
 				continue iteration
 			}
 		default:
-			panic(fmt.Errorf("Field '%s' has unsupported type %s", field.JSONName, field.Type.String()))
+			panic(fmt.Errorf("field %q has unsupported type %s", field.JSONName, field.Type.String()))
 		}
 
 		// No special treament is needed
diff --git a/jsoninfo/unmarshal.go b/jsoninfo/unmarshal.go
index 3297187..ce3c337 100644
--- a/jsoninfo/unmarshal.go
+++ b/jsoninfo/unmarshal.go
@@ -25,7 +25,7 @@ type ObjectDecoder struct {
 func NewObjectDecoder(data []byte) (*ObjectDecoder, error) {
 	var remainingFields map[string]json.RawMessage
 	if err := json.Unmarshal(data, &remainingFields); err != nil {
-		return nil, fmt.Errorf("Failed to unmarshal extension properties: %v\nInput: %s", err, data)
+		return nil, fmt.Errorf("failed to unmarshal extension properties: %v (%s)", err, data)
 	}
 	return &ObjectDecoder{
 		Data:            data,
@@ -41,10 +41,10 @@ func (decoder *ObjectDecoder) DecodeExtensionMap() map[string]json.RawMessage {
 func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) error {
 	reflection := reflect.ValueOf(value)
 	if reflection.Kind() != reflect.Ptr {
-		panic(fmt.Errorf("Value %T is not a pointer", value))
+		panic(fmt.Errorf("value %T is not a pointer", value))
 	}
 	if reflection.IsNil() {
-		panic(fmt.Errorf("Value %T is nil", value))
+		panic(fmt.Errorf("value %T is nil", value))
 	}
 	reflection = reflection.Elem()
 	for (reflection.Kind() == reflect.Interface || reflection.Kind() == reflect.Ptr) && !reflection.IsNil() {
@@ -52,7 +52,7 @@ func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{})
 	}
 	reflectionType := reflection.Type()
 	if reflectionType.Kind() != reflect.Struct {
-		panic(fmt.Errorf("Value %T is not a struct", value))
+		panic(fmt.Errorf("value %T is not a struct", value))
 	}
 	typeInfo := GetTypeInfo(reflectionType)
 
@@ -87,7 +87,7 @@ func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{})
 						continue
 					}
 				}
-				return fmt.Errorf("Error while unmarshalling property '%s' (%s): %v",
+				return fmt.Errorf("failed to unmarshal property %q (%s): %v",
 					field.JSONName, fieldValue.Type().String(), err)
 			}
 			if !isPtr {
@@ -109,7 +109,7 @@ func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{})
 						continue
 					}
 				}
-				return fmt.Errorf("Error while unmarshalling property '%s' (%s): %v",
+				return fmt.Errorf("failed to unmarshal property %q (%s): %v",
 					field.JSONName, fieldPtr.Type().String(), err)
 			}
 
diff --git a/jsoninfo/unsupported_properties_error.go b/jsoninfo/unsupported_properties_error.go
index 258efef..f69aafd 100644
--- a/jsoninfo/unsupported_properties_error.go
+++ b/jsoninfo/unsupported_properties_error.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"sort"
-	"strings"
 )
 
 // UnsupportedPropertiesError is a helper for extensions that want to refuse
@@ -27,7 +26,7 @@ func (err *UnsupportedPropertiesError) Error() string {
 	m := err.UnsupportedProperties
 	typeInfo := GetTypeInfoForValue(err.Value)
 	if m == nil || typeInfo == nil {
-		return "Invalid UnsupportedPropertiesError"
+		return fmt.Sprintf("invalid %T", *err)
 	}
 	keys := make([]string, 0, len(m))
 	for k := range m {
@@ -36,10 +35,8 @@ func (err *UnsupportedPropertiesError) Error() string {
 	sort.Strings(keys)
 	supported := typeInfo.FieldNames()
 	if len(supported) == 0 {
-		return fmt.Sprintf("Type '%T' doesn't take any properties. Unsupported properties: '%s'\n",
-			err.Value, strings.Join(keys, "', '"))
+		return fmt.Sprintf("type \"%T\" doesn't take any properties. Unsupported properties: %+v",
+			err.Value, keys)
 	}
-	return fmt.Sprintf("Unsupported properties: '%s'\nSupported properties are: '%s'",
-		strings.Join(keys, "', '"),
-		strings.Join(supported, "', '"))
+	return fmt.Sprintf("unsupported properties: %+v (supported properties are: %+v)", keys, supported)
 }
diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go
index 2a3006c..8797c57 100644
--- a/openapi2/openapi2.go
+++ b/openapi2/openapi2.go
@@ -120,7 +120,7 @@ func (pathItem *PathItem) GetOperation(method string) *Operation {
 	case http.MethodPut:
 		return pathItem.Put
 	default:
-		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
+		panic(fmt.Errorf("unsupported HTTP method %q", method))
 	}
 }
 
@@ -141,7 +141,7 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
 	case http.MethodPut:
 		pathItem.Put = operation
 	default:
-		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
+		panic(fmt.Errorf("unsupported HTTP method %q", method))
 	}
 }
 
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index f3f8c5c..2622f76 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -519,7 +519,7 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
 		case "password":
 			flows.Password = flow
 		default:
-			return nil, fmt.Errorf("Unsupported flow '%s'", securityScheme.Flow)
+			return nil, fmt.Errorf("unsupported flow %q", securityScheme.Flow)
 		}
 	}
 	return &openapi3.SecuritySchemeRef{
@@ -1077,7 +1077,7 @@ func FromV3SecurityScheme(swagger *openapi3.Swagger, ref *openapi3.SecuritySchem
 			}
 		}
 	default:
-		return nil, fmt.Errorf("Unsupported security scheme type '%s'", securityScheme.Type)
+		return nil, fmt.Errorf("unsupported security scheme type %q", securityScheme.Type)
 	}
 	return result, nil
 }
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index 16b7a26..4bf657f 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -86,7 +86,7 @@ func (encoding *Encoding) Validate(c context.Context) error {
 		sm.Style == SerializationDeepObject && sm.Explode:
 		// it is a valid
 	default:
-		return fmt.Errorf("Serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
+		return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
 	}
 
 	return nil
diff --git a/openapi3/info.go b/openapi3/info.go
index 25e675e..386ae86 100644
--- a/openapi3/info.go
+++ b/openapi3/info.go
@@ -40,11 +40,11 @@ func (value *Info) Validate(c context.Context) error {
 	}
 
 	if value.Version == "" {
-		return errors.New("value of version must be a non-empty JSON string")
+		return errors.New("value of version must be a non-empty string")
 	}
 
 	if value.Title == "" {
-		return errors.New("value of title must be a non-empty JSON string")
+		return errors.New("value of title must be a non-empty string")
 	}
 
 	return nil
@@ -87,7 +87,7 @@ func (value *License) UnmarshalJSON(data []byte) error {
 
 func (value *License) Validate(c context.Context) error {
 	if value.Name == "" {
-		return errors.New("value of license name must be a non-empty JSON string")
+		return errors.New("value of license name must be a non-empty string")
 	}
 	return nil
 }
diff --git a/openapi3/link.go b/openapi3/link.go
index 0fe1a1c..722c166 100644
--- a/openapi3/link.go
+++ b/openapi3/link.go
@@ -49,7 +49,7 @@ func (value *Link) Validate(c context.Context) error {
 		return errors.New("missing operationId or operationRef on link")
 	}
 	if value.OperationID != "" && value.OperationRef != "" {
-		return fmt.Errorf("operationId '%s' and operationRef '%s' are mutually exclusive", value.OperationID, value.OperationRef)
+		return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", value.OperationID, value.OperationRef)
 	}
 	return nil
 }
diff --git a/openapi3/operation.go b/openapi3/operation.go
index 08b4312..f7ff93f 100644
--- a/openapi3/operation.go
+++ b/openapi3/operation.go
@@ -136,7 +136,7 @@ func (operation *Operation) Validate(c context.Context) error {
 			return err
 		}
 	} else {
-		return errors.New("value of responses must be a JSON object")
+		return errors.New("value of responses must be an object")
 	}
 	return nil
 }
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 8603bd7..76e5f7f 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -38,7 +38,7 @@ func (p Parameters) JSONLookup(token string) (interface{}, error) {
 	}
 
 	if index < 0 || index >= len(p) {
-		return nil, fmt.Errorf("index out of bounds array[0,%d] index '%d'", len(p), index)
+		return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p))
 	}
 
 	ref := p[index]
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 2ed578a..f7cf1d9 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -87,7 +87,7 @@ func (pathItem *PathItem) GetOperation(method string) *Operation {
 	case http.MethodTrace:
 		return pathItem.Trace
 	default:
-		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
+		panic(fmt.Errorf("unsupported HTTP method %q", method))
 	}
 }
 
@@ -112,7 +112,7 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
 	case http.MethodTrace:
 		pathItem.Trace = operation
 	default:
-		panic(fmt.Errorf("Unsupported HTTP method '%s'", method))
+		panic(fmt.Errorf("unsupported HTTP method %q", method))
 	}
 }
 
diff --git a/openapi3/schema.go b/openapi3/schema.go
index bed51c1..c39023e 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -23,15 +23,15 @@ var (
 	//SchemaFormatValidationDisabled disables validation of schema type formats.
 	SchemaFormatValidationDisabled = false
 
-	errSchema = errors.New("Input does not match the schema")
+	errSchema = errors.New("input does not match the schema")
 
 	// ErrOneOfConflict is the SchemaError Origin when data matches more than one oneOf schema
 	ErrOneOfConflict = errors.New("input matches more than one oneOf schemas")
 
 	// ErrSchemaInputNaN may be returned when validating a number
-	ErrSchemaInputNaN = errors.New("NaN is not allowed")
+	ErrSchemaInputNaN = errors.New("floating point NaN is not allowed")
 	// ErrSchemaInputInf may be returned when validating a number
-	ErrSchemaInputInf = errors.New("Inf is not allowed")
+	ErrSchemaInputInf = errors.New("floating point Inf is not allowed")
 )
 
 // Float64Ptr is a helper for defining OpenAPI schemas.
@@ -591,7 +591,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) {
 	stack = append(stack, schema)
 
 	if schema.ReadOnly && schema.WriteOnly {
-		return errors.New("A property MUST NOT be marked as both readOnly and writeOnly being true")
+		return errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true")
 	}
 
 	for _, item := range schema.OneOf {
@@ -678,11 +678,11 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) {
 		}
 	case "array":
 		if schema.Items == nil {
-			return errors.New("When schema type is 'array', schema 'items' must be non-null")
+			return errors.New("when schema type is 'array', schema 'items' must be non-null")
 		}
 	case "object":
 	default:
-		return fmt.Errorf("Unsupported 'type' value '%s'", schemaType)
+		return fmt.Errorf("unsupported 'type' value %q", schemaType)
 	}
 
 	if ref := schema.Items; ref != nil {
@@ -791,7 +791,7 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "type",
-			Reason:      fmt.Sprintf("Not a JSON value: %T", value),
+			Reason:      fmt.Sprintf("unhandled value of type %T", value),
 		}
 	}
 }
@@ -810,7 +810,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "enum",
-			Reason:      "JSON value is not one of the allowed values",
+			Reason:      "value is not one of the allowed values",
 		}
 	}
 
@@ -994,7 +994,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "exclusiveMinimum",
-			Reason:      fmt.Sprintf("Number must be more than %g", *schema.Min),
+			Reason:      fmt.Sprintf("number must be more than %g", *schema.Min),
 		}
 		if !settings.multiError {
 			return err
@@ -1011,7 +1011,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "exclusiveMaximum",
-			Reason:      fmt.Sprintf("Number must be less than %g", *schema.Max),
+			Reason:      fmt.Sprintf("number must be less than %g", *schema.Max),
 		}
 		if !settings.multiError {
 			return err
@@ -1028,7 +1028,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "minimum",
-			Reason:      fmt.Sprintf("Number must be at least %g", *v),
+			Reason:      fmt.Sprintf("number must be at least %g", *v),
 		}
 		if !settings.multiError {
 			return err
@@ -1045,7 +1045,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "maximum",
-			Reason:      fmt.Sprintf("Number must be most %g", *v),
+			Reason:      fmt.Sprintf("number must be most %g", *v),
 		}
 		if !settings.multiError {
 			return err
@@ -1113,7 +1113,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "minLength",
-				Reason:      fmt.Sprintf("Minimum string length is %d", minLength),
+				Reason:      fmt.Sprintf("minimum string length is %d", minLength),
 			}
 			if !settings.multiError {
 				return err
@@ -1128,7 +1128,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "maxLength",
-				Reason:      fmt.Sprintf("Maximum string length is %d", *maxLength),
+				Reason:      fmt.Sprintf("maximum string length is %d", *maxLength),
 			}
 			if !settings.multiError {
 				return err
@@ -1158,7 +1158,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "pattern",
-			Reason:      fmt.Sprintf("JSON string doesn't match the regular expression %q", schema.Pattern),
+			Reason:      fmt.Sprintf("string doesn't match the regular expression %q", schema.Pattern),
 		}
 		if !settings.multiError {
 			return err
@@ -1173,7 +1173,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 			switch {
 			case f.regexp != nil && f.callback == nil:
 				if cp := f.regexp; !cp.MatchString(value) {
-					formatErr = fmt.Sprintf("JSON string doesn't match the format %q (regular expression %q)", format, cp.String())
+					formatErr = fmt.Sprintf("string doesn't match the format %q (regular expression %q)", format, cp.String())
 				}
 			case f.regexp == nil && f.callback != nil:
 				if err := f.callback(value); err != nil {
@@ -1228,7 +1228,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "minItems",
-			Reason:      fmt.Sprintf("Minimum number of items is %d", v),
+			Reason:      fmt.Sprintf("minimum number of items is %d", v),
 		}
 		if !settings.multiError {
 			return err
@@ -1245,7 +1245,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "maxItems",
-			Reason:      fmt.Sprintf("Maximum number of items is %d", *v),
+			Reason:      fmt.Sprintf("maximum number of items is %d", *v),
 		}
 		if !settings.multiError {
 			return err
@@ -1265,7 +1265,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "uniqueItems",
-			Reason:      fmt.Sprintf("Duplicate items found"),
+			Reason:      fmt.Sprintf("duplicate items found"),
 		}
 		if !settings.multiError {
 			return err
@@ -1326,7 +1326,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "minProperties",
-			Reason:      fmt.Sprintf("There must be at least %d properties", v),
+			Reason:      fmt.Sprintf("there must be at least %d properties", v),
 		}
 		if !settings.multiError {
 			return err
@@ -1343,7 +1343,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "maxProperties",
-			Reason:      fmt.Sprintf("There must be at most %d properties", *v),
+			Reason:      fmt.Sprintf("there must be at most %d properties", *v),
 		}
 		if !settings.multiError {
 			return err
@@ -1408,7 +1408,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "properties",
-			Reason:      fmt.Sprintf("Property '%s' is unsupported", k),
+			Reason:      fmt.Sprintf("property %q is unsupported", k),
 		}
 		if !settings.multiError {
 			return err
@@ -1432,7 +1432,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 				Value:       value,
 				Schema:      schema,
 				SchemaField: "required",
-				Reason:      fmt.Sprintf("Property '%s' is missing", k),
+				Reason:      fmt.Sprintf("property %q is missing", k),
 			}, k)
 			if !settings.multiError {
 				return err
@@ -1572,5 +1572,5 @@ func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) {
 }
 
 func unsupportedFormat(format string) error {
-	return fmt.Errorf("Unsupported 'format' value '%s'", format)
+	return fmt.Errorf("unsupported 'format' value %q", format)
 }
diff --git a/openapi3/schema_formats.go b/openapi3/schema_formats.go
index ab2992b..1eb4150 100644
--- a/openapi3/schema_formats.go
+++ b/openapi3/schema_formats.go
@@ -26,7 +26,7 @@ var SchemaStringFormats = make(map[string]Format, 8)
 func DefineStringFormat(name string, pattern string) {
 	re, err := regexp.Compile(pattern)
 	if err != nil {
-		err := fmt.Errorf("Format '%v' has invalid pattern '%v': %v", name, pattern, err)
+		err := fmt.Errorf("format %q has invalid pattern %q: %v", name, pattern, err)
 		panic(err)
 	}
 	SchemaStringFormats[name] = Format{regexp: re}
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index 8d6c7c1..d1f665c 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -117,16 +117,16 @@ func (ss *SecurityScheme) Validate(c context.Context) error {
 			hasBearerFormat = true
 		case "basic", "negotiate", "digest":
 		default:
-			return fmt.Errorf("Security scheme of type 'http' has invalid 'scheme' value '%s'", scheme)
+			return fmt.Errorf("security scheme of type 'http' has invalid 'scheme' value %q", scheme)
 		}
 	case "oauth2":
 		hasFlow = true
 	case "openIdConnect":
 		if ss.OpenIdConnectUrl == "" {
-			return fmt.Errorf("No OIDC URL found for openIdConnect security scheme %q", ss.Name)
+			return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", ss.Name)
 		}
 	default:
-		return fmt.Errorf("Security scheme 'type' can't be '%v'", ss.Type)
+		return fmt.Errorf("security scheme 'type' can't be %q", ss.Type)
 	}
 
 	// Validate "in" and "name"
@@ -134,34 +134,34 @@ func (ss *SecurityScheme) Validate(c context.Context) error {
 		switch ss.In {
 		case "query", "header", "cookie":
 		default:
-			return fmt.Errorf("Security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not '%s'", ss.In)
+			return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", ss.In)
 		}
 		if ss.Name == "" {
-			return errors.New("Security scheme of type 'apiKey' should have 'name'")
+			return errors.New("security scheme of type 'apiKey' should have 'name'")
 		}
 	} else if len(ss.In) > 0 {
-		return fmt.Errorf("Security scheme of type '%s' can't have 'in'", ss.Type)
+		return fmt.Errorf("security scheme of type %q can't have 'in'", ss.Type)
 	} else if len(ss.Name) > 0 {
-		return errors.New("Security scheme of type 'apiKey' can't have 'name'")
+		return errors.New("security scheme of type 'apiKey' can't have 'name'")
 	}
 
 	// Validate "format"
 	// "bearerFormat" is an arbitrary string so we only check if the scheme supports it
 	if !hasBearerFormat && len(ss.BearerFormat) > 0 {
-		return fmt.Errorf("Security scheme of type '%v' can't have 'bearerFormat'", ss.Type)
+		return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", ss.Type)
 	}
 
 	// Validate "flow"
 	if hasFlow {
 		flow := ss.Flows
 		if flow == nil {
-			return fmt.Errorf("Security scheme of type '%v' should have 'flows'", ss.Type)
+			return fmt.Errorf("security scheme of type %q should have 'flows'", ss.Type)
 		}
 		if err := flow.Validate(c); err != nil {
-			return fmt.Errorf("Security scheme 'flow' is invalid: %v", err)
+			return fmt.Errorf("security scheme 'flow' is invalid: %v", err)
 		}
 	} else if ss.Flows != nil {
-		return fmt.Errorf("Security scheme of type '%s' can't have 'flows'", ss.Type)
+		return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type)
 	}
 	return nil
 }
@@ -204,7 +204,7 @@ func (flows *OAuthFlows) Validate(c context.Context) error {
 	if v := flows.AuthorizationCode; v != nil {
 		return v.Validate(c, oAuthFlowAuthorizationCode)
 	}
-	return errors.New("No OAuth flow is defined")
+	return errors.New("no OAuth flow is defined")
 }
 
 type OAuthFlow struct {
@@ -226,16 +226,16 @@ func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
 func (flow *OAuthFlow) Validate(c context.Context, typ oAuthFlowType) error {
 	if typ == oAuthFlowAuthorizationCode || typ == oAuthFlowTypeImplicit {
 		if v := flow.AuthorizationURL; v == "" {
-			return errors.New("An OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '")
+			return errors.New("an OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '")
 		}
 	}
 	if typ != oAuthFlowTypeImplicit {
 		if v := flow.TokenURL; v == "" {
-			return errors.New("An OAuth flow is missing 'tokenUrl in not implicit'")
+			return errors.New("an OAuth flow is missing 'tokenUrl in not implicit'")
 		}
 	}
 	if v := flow.Scopes; v == nil {
-		return errors.New("An OAuth flow is missing 'scopes'")
+		return errors.New("an OAuth flow is missing 'scopes'")
 	}
 	return nil
 }
diff --git a/openapi3/server.go b/openapi3/server.go
index eeeae06..56dd1d8 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -64,7 +64,7 @@ func (server Server) ParameterNames() ([]string, error) {
 		pattern = pattern[i+1:]
 		i = strings.IndexByte(pattern, '}')
 		if i < 0 {
-			return nil, errors.New("Missing '}'")
+			return nil, errors.New("missing '}'")
 		}
 		params = append(params, strings.TrimSpace(pattern[:i]))
 		pattern = pattern[i+1:]
@@ -126,7 +126,7 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) {
 
 func (server *Server) Validate(c context.Context) (err error) {
 	if server.URL == "" {
-		return errors.New("value of url must be a non-empty JSON string")
+		return errors.New("value of url must be a non-empty string")
 	}
 	for _, v := range server.Variables {
 		if err = v.Validate(c); err != nil {
@@ -156,13 +156,13 @@ func (serverVariable *ServerVariable) Validate(c context.Context) error {
 	switch serverVariable.Default.(type) {
 	case float64, string:
 	default:
-		return errors.New("value of default must be either JSON number or JSON string")
+		return errors.New("value of default must be either a number or a string")
 	}
 	for _, item := range serverVariable.Enum {
 		switch item.(type) {
 		case float64, string:
 		default:
-			return errors.New("All 'enum' items must be either a number or a string")
+			return errors.New("all 'enum' items must be either a number or a string")
 		}
 	}
 	return nil
diff --git a/openapi3/swagger.go b/openapi3/swagger.go
index 06be8a3..64f76c2 100644
--- a/openapi3/swagger.go
+++ b/openapi3/swagger.go
@@ -48,7 +48,7 @@ func (swagger *Swagger) AddServer(server *Server) {
 
 func (swagger *Swagger) Validate(c context.Context) error {
 	if swagger.OpenAPI == "" {
-		return errors.New("value of openapi must be a non-empty JSON string")
+		return errors.New("value of openapi must be a non-empty string")
 	}
 
 	// NOTE: only mention info/components/paths/... key in this func's errors.
@@ -67,7 +67,7 @@ func (swagger *Swagger) Validate(c context.Context) error {
 				return wrap(err)
 			}
 		} else {
-			return wrap(errors.New("must be a JSON object"))
+			return wrap(errors.New("must be an object"))
 		}
 	}
 
@@ -78,7 +78,7 @@ func (swagger *Swagger) Validate(c context.Context) error {
 				return wrap(err)
 			}
 		} else {
-			return wrap(errors.New("must be a JSON object"))
+			return wrap(errors.New("must be an object"))
 		}
 	}
 
diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go
index 3bb902d..8ae8d04 100644
--- a/openapi3/swagger_loader.go
+++ b/openapi3/swagger_loader.go
@@ -71,8 +71,7 @@ func (swaggerLoader *SwaggerLoader) loadSwaggerFromURIInternal(location *url.URL
 	return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, location)
 }
 
-// loadSingleElementFromURI read the data from ref and unmarshal to JSON to the
-// passed element.
+// loadSingleElementFromURI reads the data from ref and unmarshals to the passed element.
 func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) error {
 	if !swaggerLoader.IsExternalRefsAllowed {
 		return fmt.Errorf("encountered non-allowed external reference: %q", ref)
@@ -428,7 +427,7 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 
 	const prefix = "#/components/headers/"
 	if component == nil {
-		return errors.New("invalid header: value MUST be a JSON object")
+		return errors.New("invalid header: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
@@ -475,7 +474,7 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
 
 	const prefix = "#/components/parameters/"
 	if component == nil {
-		return errors.New("invalid parameter: value MUST be a JSON object")
+		return errors.New("invalid parameter: value MUST be an object")
 	}
 	ref := component.Ref
 	if ref != "" {
@@ -538,7 +537,7 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 
 	const prefix = "#/components/requestBodies/"
 	if component == nil {
-		return errors.New("invalid requestBody: value MUST be a JSON object")
+		return errors.New("invalid requestBody: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
@@ -593,7 +592,7 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 
 	const prefix = "#/components/responses/"
 	if component == nil {
-		return errors.New("invalid response: value MUST be a JSON object")
+		return errors.New("invalid response: value MUST be an object")
 	}
 	ref := component.Ref
 	if ref != "" {
@@ -667,7 +666,7 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 
 	const prefix = "#/components/schemas/"
 	if component == nil {
-		return errors.New("invalid schema: value MUST be a JSON object")
+		return errors.New("invalid schema: value MUST be an object")
 	}
 	ref := component.Ref
 	if ref != "" {
@@ -753,7 +752,7 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c
 
 	const prefix = "#/components/securitySchemes/"
 	if component == nil {
-		return errors.New("invalid securityScheme: value MUST be a JSON object")
+		return errors.New("invalid securityScheme: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
@@ -791,7 +790,7 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 
 	const prefix = "#/components/examples/"
 	if component == nil {
-		return errors.New("invalid example: value MUST be a JSON object")
+		return errors.New("invalid example: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
@@ -829,7 +828,7 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *
 
 	const prefix = "#/components/links/"
 	if component == nil {
-		return errors.New("invalid link: value MUST be a JSON object")
+		return errors.New("invalid link: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
@@ -867,7 +866,7 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
 
 	const prefix = "#/paths/"
 	if pathItem == nil {
-		return errors.New("invalid path item: value MUST be a JSON object")
+		return errors.New("invalid path item: value MUST be an object")
 	}
 	ref := pathItem.Ref
 	if ref != "" {
diff --git a/openapi3filter/authentication_input.go b/openapi3filter/authentication_input.go
index bae7c43..a53484b 100644
--- a/openapi3filter/authentication_input.go
+++ b/openapi3filter/authentication_input.go
@@ -2,7 +2,6 @@ package openapi3filter
 
 import (
 	"fmt"
-	"strings"
 
 	"github.com/getkin/kin-openapi/openapi3"
 )
@@ -16,19 +15,15 @@ type AuthenticationInput struct {
 
 func (input *AuthenticationInput) NewError(err error) error {
 	if err == nil {
-		scopes := input.Scopes
-		if len(scopes) == 0 {
-			err = fmt.Errorf("Security requirement '%s' failed",
-				input.SecuritySchemeName)
+		if len(input.Scopes) == 0 {
+			err = fmt.Errorf("security requirement %q failed", input.SecuritySchemeName)
 		} else {
-			err = fmt.Errorf("Security requirement '%s' (scopes: '%s') failed",
-				input.SecuritySchemeName,
-				strings.Join(input.Scopes, "', '"))
+			err = fmt.Errorf("security requirement %q (scopes: %+v) failed", input.SecuritySchemeName, input.Scopes)
 		}
 	}
 	return &RequestError{
 		Input:  input.RequestValidationInput,
-		Reason: "Authorization failed",
+		Reason: "authorization failed",
 		Err:    err,
 	}
 }
diff --git a/openapi3filter/errors.go b/openapi3filter/errors.go
index 9b46ebd..f4c9f0b 100644
--- a/openapi3filter/errors.go
+++ b/openapi3filter/errors.go
@@ -1,19 +1,11 @@
 package openapi3filter
 
 import (
-	"errors"
 	"fmt"
-	"net/http"
 
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
-var (
-	errRouteMissingSwagger          = errors.New("Route is missing OpenAPI specification")
-	errRouteMissingOperation        = errors.New("Route is missing OpenAPI operation")
-	ErrAuthenticationServiceMissing = errors.New("Request validator doesn't have an authentication service defined")
-)
-
 type RouteError struct {
 	Route  Route
 	Reason string
@@ -23,23 +15,17 @@ func (err *RouteError) Error() string {
 	return err.Reason
 }
 
+var _ error = &RequestError{}
+
+// RequestError is returned by ValidateRequest when request does not match OpenAPI spec
 type RequestError struct {
 	Input       *RequestValidationInput
 	Parameter   *openapi3.Parameter
 	RequestBody *openapi3.RequestBody
-	Status      int
 	Reason      string
 	Err         error
 }
 
-func (err *RequestError) HTTPStatus() int {
-	status := err.Status
-	if status == 0 {
-		status = http.StatusBadRequest
-	}
-	return status
-}
-
 func (err *RequestError) Error() string {
 	reason := err.Reason
 	if e := err.Err; e != nil {
@@ -50,14 +36,17 @@ func (err *RequestError) Error() string {
 		}
 	}
 	if v := err.Parameter; v != nil {
-		return fmt.Sprintf("Parameter '%s' in %s has an error: %s", v.Name, v.In, reason)
+		return fmt.Sprintf("parameter %q in %s has an error: %s", v.Name, v.In, reason)
 	} else if v := err.RequestBody; v != nil {
-		return fmt.Sprintf("Request body has an error: %s", reason)
+		return fmt.Sprintf("request body has an error: %s", reason)
 	} else {
 		return reason
 	}
 }
 
+var _ error = &ResponseError{}
+
+// ResponseError is returned by ValidateResponse when response does not match OpenAPI spec
 type ResponseError struct {
 	Input  *ResponseValidationInput
 	Reason string
@@ -76,6 +65,10 @@ func (err *ResponseError) Error() string {
 	return reason
 }
 
+var _ error = &SecurityRequirementsError{}
+
+// SecurityRequirementsError is returned by ValidateSecurityRequirements
+// when no requirement is met.
 type SecurityRequirementsError struct {
 	SecurityRequirements openapi3.SecurityRequirements
 	Errors               []error
diff --git a/openapi3filter/options.go b/openapi3filter/options.go
index b0fb39d..1622339 100644
--- a/openapi3filter/options.go
+++ b/openapi3filter/options.go
@@ -7,8 +7,14 @@ var DefaultOptions = &Options{}
 
 // Options used by ValidateRequest and ValidateResponse
 type Options struct {
-	ExcludeRequestBody    bool
-	ExcludeResponseBody   bool
+	// Set ExcludeRequestBody so ValidateRequest skips request body validation
+	ExcludeRequestBody bool
+
+	// Set ExcludeResponseBody so ValidateResponse skips response body validation
+	ExcludeResponseBody bool
+
+	// Set IncludeResponseStatus so ValidateResponse fails on response
+	// status not defined in OpenAPI spec
 	IncludeResponseStatus bool
 
 	MultiError bool
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 163bc69..d192664 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -130,13 +130,13 @@ func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationI
 			found = true
 		}
 	default:
-		err = fmt.Errorf("unsupported parameter's 'in': %s", param.In)
+		err = fmt.Errorf("unsupported parameter.in: %q", param.In)
 		return
 	}
 
 	if !found {
 		if param.Required {
-			err = fmt.Errorf("parameter '%s' is required, but missing", param.Name)
+			err = fmt.Errorf("parameter %q is required, but missing", param.Name)
 		}
 		return
 	}
@@ -154,32 +154,32 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string)
 	outValue interface{}, outSchema *openapi3.Schema, err error) {
 	// Only query parameters can have multiple values.
 	if len(values) > 1 && param.In != openapi3.ParameterInQuery {
-		err = fmt.Errorf("%s parameter '%s' can't have multiple values", param.In, param.Name)
+		err = fmt.Errorf("%s parameter %q cannot have multiple values", param.In, param.Name)
 		return
 	}
 
 	content := param.Content
 	if content == nil {
-		err = fmt.Errorf("parameter '%s' expected to have content", param.Name)
+		err = fmt.Errorf("parameter %q expected to have content", param.Name)
 		return
 	}
 
 	// We only know how to decode a parameter if it has one content, application/json
 	if len(content) != 1 {
-		err = fmt.Errorf("multiple content types for parameter '%s'", param.Name)
+		err = fmt.Errorf("multiple content types for parameter %q", param.Name)
 		return
 	}
 
 	mt := content.Get("application/json")
 	if mt == nil {
-		err = fmt.Errorf("parameter '%s' has no json content schema", param.Name)
+		err = fmt.Errorf("parameter %q has no content schema", param.Name)
 		return
 	}
 	outSchema = mt.Schema.Value
 
 	if len(values) == 1 {
 		if err = json.Unmarshal([]byte(values[0]), &outValue); err != nil {
-			err = fmt.Errorf("error unmarshaling parameter '%s' as json", param.Name)
+			err = fmt.Errorf("error unmarshaling parameter %q", param.Name)
 			return
 		}
 	} else {
@@ -187,7 +187,7 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string)
 		for _, v := range values {
 			var item interface{}
 			if err = json.Unmarshal([]byte(v), &item); err != nil {
-				err = fmt.Errorf("error unmarshaling parameter '%s' as json", param.Name)
+				err = fmt.Errorf("error unmarshaling parameter %q", param.Name)
 				return
 			}
 			outArray = append(outArray, item)
@@ -257,12 +257,10 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 				return value, nil
 			}
 		}
-		if required == true {
+		if required {
 			return nil, fmt.Errorf("decoding anyOf for parameter %q failed", param)
-		} else {
-			return nil, nil
 		}
-
+		return nil, nil
 	}
 
 	if len(schema.Value.OneOf) > 0 {
@@ -280,12 +278,12 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 		} else if isMatched > 1 {
 			return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
 		}
-		if required == true {
+		if required {
 			return nil, fmt.Errorf("decoding oneOf failed: %q is required", param)
-		} else {
-			return nil, nil
 		}
+		return nil, nil
 	}
+
 	if schema.Value.Not != nil {
 		// TODO(decode not): handle decoding "not" JSON Schema
 		return nil, errors.New("not implemented: decoding 'not'")
@@ -306,6 +304,7 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 		}
 		return decodeFn(param, sm, schema)
 	}
+
 	return nil, nil
 }
 
@@ -331,7 +330,7 @@ func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.Serializat
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
 	}
-	raw, ok := d.pathParams[paramKey(param, sm)]
+	raw, ok := d.pathParams[param]
 	if !ok || raw == "" {
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
@@ -368,7 +367,7 @@ func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationM
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
 	}
-	raw, ok := d.pathParams[paramKey(param, sm)]
+	raw, ok := d.pathParams[param]
 	if !ok || raw == "" {
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
@@ -383,25 +382,25 @@ func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationM
 func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
 	var prefix, propsDelim, valueDelim string
 	switch {
-	case sm.Style == "simple" && sm.Explode == false:
+	case sm.Style == "simple" && !sm.Explode:
 		propsDelim = ","
 		valueDelim = ","
-	case sm.Style == "simple" && sm.Explode == true:
+	case sm.Style == "simple" && sm.Explode:
 		propsDelim = ","
 		valueDelim = "="
-	case sm.Style == "label" && sm.Explode == false:
+	case sm.Style == "label" && !sm.Explode:
 		prefix = "."
 		propsDelim = ","
 		valueDelim = ","
-	case sm.Style == "label" && sm.Explode == true:
+	case sm.Style == "label" && sm.Explode:
 		prefix = "."
 		propsDelim = "."
 		valueDelim = "="
-	case sm.Style == "matrix" && sm.Explode == false:
+	case sm.Style == "matrix" && !sm.Explode:
 		prefix = ";" + param + "="
 		propsDelim = ","
 		valueDelim = ","
-	case sm.Style == "matrix" && sm.Explode == true:
+	case sm.Style == "matrix" && sm.Explode:
 		prefix = ";"
 		propsDelim = ";"
 		valueDelim = "="
@@ -413,7 +412,7 @@ func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.Serialization
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
 	}
-	raw, ok := d.pathParams[paramKey(param, sm)]
+	raw, ok := d.pathParams[param]
 	if !ok || raw == "" {
 		// HTTP request does not contains a value of the target path parameter.
 		return nil, nil
@@ -429,18 +428,6 @@ func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.Serialization
 	return makeObject(props, schema)
 }
 
-// paramKey returns a key to get a raw value of a path parameter.
-func paramKey(param string, sm *openapi3.SerializationMethod) string {
-	switch sm.Style {
-	case "label":
-		return "." + param
-	case "matrix":
-		return ";" + param
-	default:
-		return param
-	}
-}
-
 // cutPrefix validates that a raw value of a path parameter has the specified prefix,
 // and returns a raw value without the prefix.
 func cutPrefix(raw, prefix string) (string, error) {
@@ -737,8 +724,8 @@ func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, err
 }
 
 // parsePrimitive returns a value that is created by parsing a source string to a primitive type
-// that is specified by a JSON schema. The function returns nil when the source string is empty.
-// The function panics when a JSON schema has a non primitive type.
+// that is specified by a schema. The function returns nil when the source string is empty.
+// The function panics when a schema has a non primitive type.
 func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error) {
 	if raw == "" {
 		return nil, nil
@@ -804,16 +791,20 @@ func UnregisterBodyDecoder(contentType string) {
 	delete(bodyDecoders, contentType)
 }
 
+var headerCT = http.CanonicalHeaderKey("Content-Type")
+
+const prefixUnsupportedCT = "unsupported content type"
+
 // decodeBody returns a decoded body.
 // The function returns ParseError when a body is invalid.
 func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
-	contentType := header.Get(http.CanonicalHeaderKey("Content-Type"))
+	contentType := header.Get(headerCT)
 	mediaType := parseMediaType(contentType)
 	decoder, ok := bodyDecoders[mediaType]
 	if !ok {
 		return nil, &ParseError{
 			Kind:   KindUnsupportedFormat,
-			Reason: fmt.Sprintf("unsupported content type %q", mediaType),
+			Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType),
 		}
 	}
 	value, err := decoder(body, header, schema, encFn)
@@ -849,20 +840,20 @@ func jsonBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema
 }
 
 func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
-	// Validate JSON schema of request body.
+	// Validate schema of request body.
 	// By the OpenAPI 3 specification request body's schema must have type "object".
 	// Properties of the schema describes individual parts of request body.
 	if schema.Value.Type != "object" {
-		return nil, errors.New("unsupported JSON schema of request body")
+		return nil, errors.New("unsupported schema of request body")
 	}
 	for propName, propSchema := range schema.Value.Properties {
 		switch propSchema.Value.Type {
 		case "object":
-			return nil, fmt.Errorf("unsupported JSON schema of request body's property %q", propName)
+			return nil, fmt.Errorf("unsupported schema of request body's property %q", propName)
 		case "array":
 			items := propSchema.Value.Items.Value
 			if items.Type != "string" && items.Type != "integer" && items.Type != "number" && items.Type != "boolean" {
-				return nil, fmt.Errorf("unsupported JSON schema of request body's property %q", propName)
+				return nil, fmt.Errorf("unsupported schema of request body's property %q", propName)
 			}
 		}
 	}
@@ -901,12 +892,12 @@ func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.
 
 func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
 	if schema.Value.Type != "object" {
-		return nil, errors.New("unsupported JSON schema of request body")
+		return nil, errors.New("unsupported schema of request body")
 	}
 
 	// Parse form.
 	values := make(map[string][]interface{})
-	contentType := header.Get(http.CanonicalHeaderKey("Content-Type"))
+	contentType := header.Get(headerCT)
 	_, params, err := mime.ParseMediaType(contentType)
 	if err != nil {
 		return nil, err
diff --git a/openapi3filter/router.go b/openapi3filter/router.go
index 3904750..3c7690d 100644
--- a/openapi3filter/router.go
+++ b/openapi3filter/router.go
@@ -12,6 +12,13 @@ import (
 	"github.com/getkin/kin-openapi/pathpattern"
 )
 
+// ErrPathNotFound is returned when no route match is found
+var ErrPathNotFound = errors.New("no matching operation was found")
+
+// ErrMethodNotAllowed is returned when no method of the matched route matches
+var ErrMethodNotAllowed = errors.New("method not allowed")
+
+// Route describes the operation an http.Request can match
 type Route struct {
 	Swagger   *openapi3.Swagger
 	Server    *openapi3.Server
@@ -27,6 +34,7 @@ type Route struct {
 // Routers maps a HTTP request to a Router.
 type Routers []*Router
 
+// FindRoute extracts the route and parameters of an http.Request
 func (routers Routers) FindRoute(method string, url *url.URL) (*Router, *Route, map[string]string, error) {
 	for _, router := range routers {
 		// Skip routers that have DO NOT have servers
@@ -97,7 +105,7 @@ func (router *Router) AddSwaggerFromFile(path string) error {
 // AddSwagger adds all operations in the OpenAPI specification.
 func (router *Router) AddSwagger(swagger *openapi3.Swagger) error {
 	if err := swagger.Validate(context.TODO()); err != nil {
-		return fmt.Errorf("Validating Swagger failed: %v", err)
+		return fmt.Errorf("validating OpenAPI failed: %v", err)
 	}
 	router.swagger = swagger
 	root := router.node()
@@ -122,12 +130,12 @@ func (router *Router) AddSwagger(swagger *openapi3.Swagger) error {
 func (router *Router) AddRoute(route *Route) error {
 	method := route.Method
 	if method == "" {
-		return errors.New("Route is missing method")
+		return errors.New("route is missing method")
 	}
 	method = strings.ToUpper(method)
 	path := route.Path
 	if path == "" {
-		return errors.New("Route is missing path")
+		return errors.New("route is missing path")
 	}
 	return router.node().Add(method+" "+path, router, nil)
 }
@@ -141,6 +149,7 @@ func (router *Router) node() *pathpattern.Node {
 	return root
 }
 
+// FindRoute extracts the route and parameters of an http.Request
 func (router *Router) FindRoute(method string, url *url.URL) (*Route, map[string]string, error) {
 	swagger := router.swagger
 
@@ -159,7 +168,7 @@ func (router *Router) FindRoute(method string, url *url.URL) (*Route, map[string
 				Route: Route{
 					Swagger: swagger,
 				},
-				Reason: "Does not match any server",
+				Reason: ErrPathNotFound.Error(),
 			}
 		}
 		pathParams = make(map[string]string, 8)
@@ -185,7 +194,7 @@ func (router *Router) FindRoute(method string, url *url.URL) (*Route, map[string
 					Swagger: swagger,
 					Server:  server,
 				},
-				Reason: "Path was not found",
+				Reason: ErrPathNotFound.Error(),
 			}
 		}
 
@@ -196,7 +205,7 @@ func (router *Router) FindRoute(method string, url *url.URL) (*Route, map[string
 					Swagger: swagger,
 					Server:  server,
 				},
-				Reason: "Path doesn't support the HTTP method",
+				Reason: ErrMethodNotAllowed.Error(),
 			}
 		}
 
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 0af54c2..f1e7bf9 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -12,8 +12,12 @@ import (
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
-// ErrInvalidRequired is an error that happens when a required value of a parameter or request's body is not defined.
-var ErrInvalidRequired = errors.New("must have a value")
+// ErrAuthenticationServiceMissing is returned when no authentication service
+// is defined for the request validator
+var ErrAuthenticationServiceMissing = errors.New("missing AuthenticationFunc")
+
+// ErrInvalidRequired is returned when a required value of a parameter or request body is not defined.
+var ErrInvalidRequired = errors.New("value is required but missing")
 
 // ValidateRequest is used to validate the given input according to previous
 // loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a
@@ -32,13 +36,7 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error {
 		options = DefaultOptions
 	}
 	route := input.Route
-	if route == nil {
-		return errors.New("invalid route")
-	}
 	operation := route.Operation
-	if operation == nil {
-		return errRouteMissingOperation
-	}
 	operationParameters := operation.Parameters
 	pathItemParameters := route.PathItem.Parameters
 
@@ -87,9 +85,6 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error {
 	security := operation.Security
 	// If there aren't any security requirements for the operation
 	if security == nil {
-		if route.Swagger == nil {
-			return errRouteMissingSwagger
-		}
 		// Use the global security requirements.
 		security = &route.Swagger.Security
 	}
@@ -145,7 +140,7 @@ func ValidateParameter(c context.Context, input *RequestValidationInput, paramet
 	// Validate a parameter's value.
 	if value == nil {
 		if parameter.Required {
-			return &RequestError{Input: input, Parameter: parameter, Reason: "must have a value", Err: ErrInvalidRequired}
+			return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidRequired.Error(), Err: ErrInvalidRequired}
 		}
 		return nil
 	}
@@ -165,6 +160,8 @@ func ValidateParameter(c context.Context, input *RequestValidationInput, paramet
 	return nil
 }
 
+const prefixInvalidCT = "header Content-Type has unexpected value"
+
 // ValidateRequestBody validates data of a request's body.
 //
 // The function returns RequestError with ErrInvalidRequired cause when a value is required but not defined.
@@ -208,13 +205,13 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque
 		return nil
 	}
 
-	inputMIME := req.Header.Get("Content-Type")
+	inputMIME := req.Header.Get(headerCT)
 	contentType := requestBody.Content.Get(inputMIME)
 	if contentType == nil {
 		return &RequestError{
 			Input:       input,
 			RequestBody: requestBody,
-			Reason:      fmt.Sprintf("header 'Content-Type' has unexpected value: %q", inputMIME),
+			Reason:      fmt.Sprintf("%s %q", prefixInvalidCT, inputMIME),
 		}
 	}
 
@@ -279,9 +276,6 @@ func ValidateSecurityRequirements(c context.Context, input *RequestValidationInp
 // validateSecurityRequirement validates a single OpenAPI 3 security requirement
 func validateSecurityRequirement(c context.Context, input *RequestValidationInput, securityRequirement openapi3.SecurityRequirement) error {
 	swagger := input.Route.Swagger
-	if swagger == nil {
-		return errRouteMissingSwagger
-	}
 	securitySchemes := swagger.Components.SecuritySchemes
 
 	// Ensure deterministic order
@@ -312,7 +306,7 @@ func validateSecurityRequirement(c context.Context, input *RequestValidationInpu
 		if securityScheme == nil {
 			return &RequestError{
 				Input: input,
-				Err:   fmt.Errorf("Security scheme '%s' is not declared", name),
+				Err:   fmt.Errorf("security scheme %q is not declared", name),
 			}
 		}
 		scopes := securityRequirement[name]
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index f203802..9575c4c 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -24,13 +24,6 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
 		return nil
 	}
 	status := input.Status
-	if status < 100 {
-		return &ResponseError{
-			Input:  input,
-			Reason: "illegal status code",
-			Err:    fmt.Errorf("Status %d", status),
-		}
-	}
 
 	// These status codes will never be validated.
 	// TODO: The list is probably missing some.
@@ -61,7 +54,6 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
 		if !options.IncludeResponseStatus {
 			return nil
 		}
-
 		return &ResponseError{Input: input, Reason: "status is not supported"}
 	}
 	response := responseRef.Value
@@ -80,12 +72,12 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
 		return nil
 	}
 
-	inputMIME := input.Header.Get("Content-Type")
+	inputMIME := input.Header.Get(headerCT)
 	contentType := content.Get(inputMIME)
 	if contentType == nil {
 		return &ResponseError{
 			Input:  input,
-			Reason: fmt.Sprintf("input header 'Content-Type' has unexpected value: %q", inputMIME),
+			Reason: fmt.Sprintf("input header Content-Type has unexpected value: %q", inputMIME),
 		}
 	}
 
diff --git a/openapi3filter/validation_error_encoder.go b/openapi3filter/validation_error_encoder.go
index 34e4af9..7c7ba5d 100644
--- a/openapi3filter/validation_error_encoder.go
+++ b/openapi3filter/validation_error_encoder.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"fmt"
 	"net/http"
-	"regexp"
 	"strings"
 
 	"github.com/getkin/kin-openapi/openapi3"
@@ -17,10 +16,8 @@ type ValidationErrorEncoder struct {
 
 // Encode implements the ErrorEncoder interface for encoding ValidationErrors
 func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http.ResponseWriter) {
-	var cErr *ValidationError
-
 	if e, ok := err.(*RouteError); ok {
-		cErr = convertRouteError(e)
+		cErr := convertRouteError(e)
 		enc.Encoder(ctx, cErr, w)
 		return
 	}
@@ -31,6 +28,7 @@ func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http
 		return
 	}
 
+	var cErr *ValidationError
 	if e.Err == nil {
 		cErr = convertBasicRequestError(e)
 	} else if e.Err == ErrInvalidRequired {
@@ -43,95 +41,80 @@ func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http
 
 	if cErr != nil {
 		enc.Encoder(ctx, cErr, w)
-	} else {
-		enc.Encoder(ctx, err, w)
+		return
 	}
+	enc.Encoder(ctx, err, w)
 }
 
 func convertRouteError(e *RouteError) *ValidationError {
-	var cErr *ValidationError
-	switch e.Reason {
-	case "Path doesn't support the HTTP method":
-		cErr = &ValidationError{Status: http.StatusMethodNotAllowed, Title: e.Reason}
-	default:
-		cErr = &ValidationError{Status: http.StatusNotFound, Title: e.Reason}
+	status := http.StatusNotFound
+	if e.Reason == ErrMethodNotAllowed.Error() {
+		status = http.StatusMethodNotAllowed
 	}
-	return cErr
+	return &ValidationError{Status: status, Title: e.Reason}
 }
 
 func convertBasicRequestError(e *RequestError) *ValidationError {
-	var cErr *ValidationError
-	unsupportedContentType := "header 'Content-Type' has unexpected value: "
-	if strings.HasPrefix(e.Reason, unsupportedContentType) {
-		if strings.HasSuffix(e.Reason, `: ""`) {
-			cErr = &ValidationError{
+	if strings.HasPrefix(e.Reason, prefixInvalidCT) {
+		if strings.HasSuffix(e.Reason, `""`) {
+			return &ValidationError{
 				Status: http.StatusUnsupportedMediaType,
-				Title:  "header 'Content-Type' is required",
-			}
-		} else {
-			cErr = &ValidationError{
-				Status: http.StatusUnsupportedMediaType,
-				Title:  "unsupported content type " + strings.TrimPrefix(e.Reason, unsupportedContentType),
+				Title:  "header Content-Type is required",
 			}
 		}
-	} else {
-		cErr = &ValidationError{
-			Status: http.StatusBadRequest,
-			Title:  e.Error(),
+		return &ValidationError{
+			Status: http.StatusUnsupportedMediaType,
+			Title:  prefixUnsupportedCT + strings.TrimPrefix(e.Reason, prefixInvalidCT),
 		}
 	}
-	return cErr
+	return &ValidationError{
+		Status: http.StatusBadRequest,
+		Title:  e.Error(),
+	}
 }
 
 func convertErrInvalidRequired(e *RequestError) *ValidationError {
-	var cErr *ValidationError
 	if e.Reason == ErrInvalidRequired.Error() && e.Parameter != nil {
-		cErr = &ValidationError{
+		return &ValidationError{
 			Status: http.StatusBadRequest,
-			Title:  fmt.Sprintf("Parameter '%s' in %s is required", e.Parameter.Name, e.Parameter.In),
-		}
-	} else {
-		cErr = &ValidationError{
-			Status: http.StatusBadRequest,
-			Title:  e.Error(),
+			Title:  fmt.Sprintf("parameter %q in %s is required", e.Parameter.Name, e.Parameter.In),
 		}
 	}
-	return cErr
+	return &ValidationError{
+		Status: http.StatusBadRequest,
+		Title:  e.Error(),
+	}
 }
 
 func convertParseError(e *RequestError, innerErr *ParseError) *ValidationError {
-	var cErr *ValidationError
 	// We treat path params of the wrong type like a 404 instead of a 400
 	if innerErr.Kind == KindInvalidFormat && e.Parameter != nil && e.Parameter.In == "path" {
-		cErr = &ValidationError{
+		return &ValidationError{
 			Status: http.StatusNotFound,
-			Title:  fmt.Sprintf("Resource not found with '%s' value: %v", e.Parameter.Name, innerErr.Value),
+			Title:  fmt.Sprintf("resource not found with %q value: %v", e.Parameter.Name, innerErr.Value),
 		}
-	} else if strings.HasPrefix(innerErr.Reason, "unsupported content type") {
-		cErr = &ValidationError{
+	} else if strings.HasPrefix(innerErr.Reason, prefixUnsupportedCT) {
+		return &ValidationError{
 			Status: http.StatusUnsupportedMediaType,
 			Title:  innerErr.Reason,
 		}
 	} else if innerErr.RootCause() != nil {
 		if rootErr, ok := innerErr.Cause.(*ParseError); ok &&
 			rootErr.Kind == KindInvalidFormat && e.Parameter.In == "query" {
-			cErr = &ValidationError{
+			return &ValidationError{
 				Status: http.StatusBadRequest,
-				Title: fmt.Sprintf("Parameter '%s' in %s is invalid: %v is %s",
+				Title: fmt.Sprintf("parameter %q in %s is invalid: %v is %s",
 					e.Parameter.Name, e.Parameter.In, rootErr.Value, rootErr.Reason),
 			}
-		} else {
-			cErr = &ValidationError{
-				Status: http.StatusBadRequest,
-				Title:  innerErr.Reason,
-			}
+		}
+		return &ValidationError{
+			Status: http.StatusBadRequest,
+			Title:  innerErr.Reason,
 		}
 	}
-	return cErr
+	return nil
 }
 
-var propertyMissingNameRE = regexp.MustCompile(`Property '(?P<name>[^']*)' is missing`)
-
 func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *ValidationError {
 	cErr := &ValidationError{Title: innerErr.Reason}
 
@@ -151,34 +134,31 @@ func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *Valida
 	if e.Parameter != nil {
 		// We have a JSONPointer in the query param too so need to
 		// make sure 'Parameter' check takes priority over 'Pointer'
-		cErr.Source = &ValidationErrorSource{
-			Parameter: e.Parameter.Name,
-		}
-	} else if innerErr.JSONPointer() != nil {
-		pointer := innerErr.JSONPointer()
-
-		cErr.Source = &ValidationErrorSource{
-			Pointer: toJSONPointer(pointer),
-		}
+		cErr.Source = &ValidationErrorSource{Parameter: e.Parameter.Name}
+	} else if ptr := innerErr.JSONPointer(); ptr != nil {
+		cErr.Source = &ValidationErrorSource{Pointer: toJSONPointer(ptr)}
 	}
 
 	// Add details on allowed values for enums
-	if innerErr.SchemaField == "enum" &&
-		innerErr.Reason == "JSON value is not one of the allowed values" {
+	if innerErr.SchemaField == "enum" {
 		enums := make([]string, 0, len(innerErr.Schema.Enum))
 		for _, enum := range innerErr.Schema.Enum {
 			enums = append(enums, fmt.Sprintf("%v", enum))
 		}
-		cErr.Detail = fmt.Sprintf("Value '%v' at %s must be one of: %s",
-			innerErr.Value, toJSONPointer(innerErr.JSONPointer()), strings.Join(enums, ", "))
+		cErr.Detail = fmt.Sprintf("value %v at %s must be one of: %s",
+			innerErr.Value,
+			toJSONPointer(innerErr.JSONPointer()),
+			strings.Join(enums, ", "))
 		value := fmt.Sprintf("%v", innerErr.Value)
 		if e.Parameter != nil &&
 			(e.Parameter.Explode == nil || *e.Parameter.Explode == true) &&
 			(e.Parameter.Style == "" || e.Parameter.Style == "form") &&
 			strings.Contains(value, ",") {
 			parts := strings.Split(value, ",")
-			cErr.Detail = cErr.Detail + "; " + fmt.Sprintf("perhaps you intended '?%s=%s'",
-				e.Parameter.Name, strings.Join(parts, "&"+e.Parameter.Name+"="))
+			cErr.Detail = fmt.Sprintf("%s; perhaps you intended '?%s=%s'",
+				cErr.Detail,
+				e.Parameter.Name,
+				strings.Join(parts, "&"+e.Parameter.Name+"="))
 		}
 	}
 	return cErr
diff --git a/openapi3gen/openapi3gen.go b/openapi3gen/openapi3gen.go
index 4cf022e..f704133 100644
--- a/openapi3gen/openapi3gen.go
+++ b/openapi3gen/openapi3gen.go
@@ -15,9 +15,7 @@ import (
 // CycleError indicates that a type graph has one or more possible cycles.
 type CycleError struct{}
 
-func (err *CycleError) Error() string {
-	return "Detected JSON cycle"
-}
+func (err *CycleError) Error() string { return "detected cycle" }
 
 // Option allows tweaking SchemaRef generation
 type Option func(*generatorOpt)
diff --git a/pathpattern/node.go b/pathpattern/node.go
index 43e2959..8621998 100644
--- a/pathpattern/node.go
+++ b/pathpattern/node.go
@@ -212,7 +212,7 @@ loop:
 				// Find variable name
 				i := strings.IndexByte(remaining, '}')
 				if i < 0 {
-					return nil, fmt.Errorf("Missing '}' in: %s", path)
+					return nil, fmt.Errorf("missing '}' in: %s", path)
 				}
 				variableName := strings.TrimSpace(remaining[1:i])
 				remaining = remaining[i+1:]
@@ -247,7 +247,7 @@ loop:
 		if suffix.Kind == SuffixKindRegExp {
 			regExp, err := regexp.Compile(suffix.Pattern)
 			if err != nil {
-				return nil, fmt.Errorf("Invalid regular expression in: %s", path)
+				return nil, fmt.Errorf("invalid regular expression in: %s", path)
 			}
 			suffix.regExp = regExp
 		}


Test patch:
diff --git a/jsoninfo/unmarshal_test.go b/jsoninfo/unmarshal_test.go
index c3dd957..dd25f04 100644
--- a/jsoninfo/unmarshal_test.go
+++ b/jsoninfo/unmarshal_test.go
@@ -149,7 +149,7 @@ func TestDecodeStructFieldsAndExtensions(t *testing.T) {
 		}{}
 		err = d.DecodeStructFieldsAndExtensions(&value)
 		require.Error(t, err)
-		require.EqualError(t, err, "Error while unmarshalling property 'field1' (*int): json: cannot unmarshal string into Go value of type int")
+		require.EqualError(t, err, `failed to unmarshal property "field1" (*int): json: cannot unmarshal string into Go value of type int`)
 		require.Equal(t, 0, value.Field1)
 		require.Equal(t, 2, len(d.DecodeExtensionMap()))
 	})
diff --git a/openapi3/discriminator_test.go b/openapi3/discriminator_test.go
index e3ed22c..c855481 100644
--- a/openapi3/discriminator_test.go
+++ b/openapi3/discriminator_test.go
@@ -6,9 +6,19 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-var jsonSpecWithDiscriminator = []byte(`
+func TestParsingDiscriminator(t *testing.T) {
+	const spec = `
 {
 	"openapi": "3.0.0",
+	"info": {
+		"version": "1.0.0",
+		"title": "title",
+		"description": "desc",
+		"contact": {
+			"email": "email"
+		}
+	},
+	"paths": {},
 	"components": {
 		"schemas": {
 			"MyResponseType": {
@@ -33,10 +43,14 @@ var jsonSpecWithDiscriminator = []byte(`
 		}
 	}
 }
-`)
+`
 
-func TestParsingDiscriminator(t *testing.T) {
-	loader, err := NewSwaggerLoader().LoadSwaggerFromData(jsonSpecWithDiscriminator)
+	loader := NewSwaggerLoader()
+	doc, err := loader.LoadSwaggerFromData([]byte(spec))
 	require.NoError(t, err)
-	require.Equal(t, 2, len(loader.Components.Schemas["MyResponseType"].Value.Discriminator.Mapping))
+
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+
+	require.Equal(t, 2, len(doc.Components.Schemas["MyResponseType"].Value.Discriminator.Mapping))
 }
diff --git a/openapi3/operation_test.go b/openapi3/operation_test.go
index fe59c60..50684a3 100644
--- a/openapi3/operation_test.go
+++ b/openapi3/operation_test.go
@@ -53,7 +53,7 @@ func TestOperationValidation(t *testing.T) {
 		{
 			"when no Responses object is provided",
 			operationWithoutResponses(),
-			errors.New("value of responses must be a JSON object"),
+			errors.New("value of responses must be an object"),
 		},
 		{
 			"when a Responses object is provided",
diff --git a/openapi3/refs_test.go b/openapi3/refs_test.go
index bd79560..7de298c 100644
--- a/openapi3/refs_test.go
+++ b/openapi3/refs_test.go
@@ -109,7 +109,7 @@ components:
 `
 
 	_, err := NewSwaggerLoader().LoadSwaggerFromData([]byte(spec))
-	require.EqualError(t, err, `invalid response: value MUST be a JSON object`)
+	require.EqualError(t, err, `invalid response: value MUST be an object`)
 }
 
 func TestIssue247(t *testing.T) {
diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go
index 81898c8..91747d8 100644
--- a/openapi3/schema_test.go
+++ b/openapi3/schema_test.go
@@ -1082,7 +1082,7 @@ func testSchemaMultiError(t *testing.T, example schemaMultiErrorExample) func(*t
 						break
 					}
 				}
-				require.True(t, found, fmt.Sprintf("Missing %s error on %s", scherr.SchemaField, strings.Join(scherr.JSONPointer(), ".")))
+				require.True(t, found, fmt.Sprintf("missing %s error on %s", scherr.SchemaField, strings.Join(scherr.JSONPointer(), ".")))
 			}
 		}
 	}
diff --git a/openapi3/server_test.go b/openapi3/server_test.go
index 550eacb..0ace034 100644
--- a/openapi3/server_test.go
+++ b/openapi3/server_test.go
@@ -69,7 +69,7 @@ func TestServerValidation(t *testing.T) {
 		{
 			"when no URL is provided",
 			invalidServer(),
-			errors.New("value of url must be a non-empty JSON string"),
+			errors.New("value of url must be a non-empty string"),
 		},
 		{
 			"when a URL is provided",
diff --git a/openapi3/swagger_loader_read_from_uri_func_test.go b/openapi3/swagger_loader_read_from_uri_func_test.go
index b157678..d63a3be 100644
--- a/openapi3/swagger_loader_read_from_uri_func_test.go
+++ b/openapi3/swagger_loader_read_from_uri_func_test.go
@@ -33,7 +33,7 @@ func (l *multipleSourceSwaggerLoaderExample) LoadSwaggerFromURI(
 ) ([]byte, error) {
 	source := l.resolveSourceFromURI(location)
 	if source == nil {
-		return nil, fmt.Errorf("Unsupported URI: %q", location.String())
+		return nil, fmt.Errorf("unsupported URI: %q", location.String())
 	}
 	return source, nil
 }
diff --git a/openapi3/swagger_loader_relative_refs_test.go b/openapi3/swagger_loader_relative_refs_test.go
index 0719389..8f07468 100644
--- a/openapi3/swagger_loader_relative_refs_test.go
+++ b/openapi3/swagger_loader_relative_refs_test.go
@@ -196,7 +196,7 @@ var refTestDataEntriesResponseError = []refTestDataEntryWithErrorMessage{
 
 func TestLoadFromDataWithExternalRef(t *testing.T) {
 	for _, td := range refTestDataEntries {
-		t.Logf("testcase '%s'", td.name)
+		t.Logf("testcase %q", td.name)
 
 		spec := []byte(fmt.Sprintf(td.contentTemplate, "components.openapi.json"))
 		loader := NewSwaggerLoader()
@@ -209,7 +209,7 @@ func TestLoadFromDataWithExternalRef(t *testing.T) {
 
 func TestLoadFromDataWithExternalRefResponseError(t *testing.T) {
 	for _, td := range refTestDataEntriesResponseError {
-		t.Logf("testcase '%s'", td.name)
+		t.Logf("testcase %q", td.name)
 
 		spec := []byte(fmt.Sprintf(td.contentTemplate, "components.openapi.json"))
 		loader := NewSwaggerLoader()
@@ -222,7 +222,7 @@ func TestLoadFromDataWithExternalRefResponseError(t *testing.T) {
 
 func TestLoadFromDataWithExternalNestedRef(t *testing.T) {
 	for _, td := range refTestDataEntries {
-		t.Logf("testcase '%s'", td.name)
+		t.Logf("testcase %q", td.name)
 
 		spec := []byte(fmt.Sprintf(td.contentTemplate, "nesteddir/nestedcomponents.openapi.json"))
 		loader := NewSwaggerLoader()
@@ -801,7 +801,7 @@ var relativeDocRefsTestDataEntries = []refTestDataEntry{
 
 func TestLoadSpecWithRelativeDocumentRefs(t *testing.T) {
 	for _, td := range relativeDocRefsTestDataEntries {
-		t.Logf("testcase '%s'", td.name)
+		t.Logf("testcase %q", td.name)
 
 		spec := []byte(td.contentTemplate)
 		loader := NewSwaggerLoader()
diff --git a/openapi3/swagger_test.go b/openapi3/swagger_test.go
index 0117cea..ded2500 100644
--- a/openapi3/swagger_test.go
+++ b/openapi3/swagger_test.go
@@ -393,10 +393,10 @@ components:
 
 	tests := map[string]string{
 		spec: "",
-		strings.Replace(spec, `openapi: 3.0.2`, ``, 1):            "value of openapi must be a non-empty JSON string",
-		strings.Replace(spec, `openapi: 3.0.2`, `openapi: ''`, 1): "value of openapi must be a non-empty JSON string",
-		strings.Replace(spec, info, ``, 1):                        "invalid info: must be a JSON object",
-		strings.Replace(spec, paths, ``, 1):                       "invalid paths: must be a JSON object",
+		strings.Replace(spec, `openapi: 3.0.2`, ``, 1):            "value of openapi must be a non-empty string",
+		strings.Replace(spec, `openapi: 3.0.2`, `openapi: ''`, 1): "value of openapi must be a non-empty string",
+		strings.Replace(spec, info, ``, 1):                        "invalid info: must be an object",
+		strings.Replace(spec, paths, ``, 1):                       "invalid paths: must be an object",
 	}
 
 	for spec, expectedErr := range tests {
diff --git a/openapi3/unique_items_checker_test.go b/openapi3/unique_items_checker_test.go
index c2dc6f3..85147c6 100644
--- a/openapi3/unique_items_checker_test.go
+++ b/openapi3/unique_items_checker_test.go
@@ -32,5 +32,5 @@ func TestRegisterArrayUniqueItemsChecker(t *testing.T) {
 
 	err = schema.VisitJSON(val)
 	require.Error(t, err)
-	require.True(t, strings.HasPrefix(err.Error(), "Duplicate items found"))
+	require.True(t, strings.HasPrefix(err.Error(), "duplicate items found"))
 }
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 1cf7be3..04ecd16 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -2,6 +2,7 @@ package openapi3filter
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -903,20 +904,9 @@ func TestDecodeParameter(t *testing.T) {
 						req.AddCookie(&http.Cookie{Name: v[0], Value: v[1]})
 					}
 
-					var path string
+					path := "/test"
 					if tc.path != "" {
-						switch tc.param.Style {
-						case "label":
-							path = "." + tc.param.Name
-						case "matrix":
-							path = ";" + tc.param.Name
-						default:
-							path = tc.param.Name
-						}
-						if tc.param.Explode != nil && *tc.param.Explode {
-							path += "*"
-						}
-						path = "/{" + path + "}"
+						path += "/{" + tc.param.Name + "}"
 					}
 
 					info := &openapi3.Info{
@@ -929,12 +919,14 @@ func TestDecodeParameter(t *testing.T) {
 						Parameters:  []*openapi3.ParameterRef{{Value: tc.param}},
 						Responses:   openapi3.NewResponses(),
 					}
-					spec.AddOperation("/test"+path, http.MethodGet, op)
+					spec.AddOperation(path, http.MethodGet, op)
+					err = spec.Validate(context.Background())
+					require.NoError(t, err)
 					router := NewRouter()
-					require.NoError(t, router.AddSwagger(spec), "failed to create a router")
+					require.NoError(t, router.AddSwagger(spec))
 
 					route, pathParams, err := router.FindRoute(req.Method, req.URL)
-					require.NoError(t, err, "failed to find a route")
+					require.NoError(t, err)
 
 					input := &RequestValidationInput{Request: req, PathParams: pathParams, Route: route}
 					got, err := decodeStyledParameter(tc.param, input)
@@ -1015,7 +1007,7 @@ func TestDecodeBody(t *testing.T) {
 		wantErr  error
 	}{
 		{
-			name:    "unsupported content type",
+			name:    prefixUnsupportedCT,
 			mime:    "application/xml",
 			wantErr: &ParseError{Kind: KindUnsupportedFormat},
 		},
@@ -1134,7 +1126,7 @@ func TestDecodeBody(t *testing.T) {
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			h := make(http.Header)
-			h.Set(http.CanonicalHeaderKey("Content-Type"), tc.mime)
+			h.Set(headerCT, tc.mime)
 			var schemaRef *openapi3.SchemaRef
 			if tc.schema != nil {
 				schemaRef = tc.schema.NewRef()
@@ -1180,7 +1172,7 @@ func newTestMultipartForm(parts []*testFormPart) (io.Reader, string, error) {
 		}
 
 		h := make(textproto.MIMEHeader)
-		h.Set("Content-Type", p.contentType)
+		h.Set(headerCT, p.contentType)
 		h.Set("Content-Disposition", disp)
 		pw, err := w.CreatePart(h)
 		if err != nil {
@@ -1214,7 +1206,7 @@ func TestRegisterAndUnregisterBodyDecoder(t *testing.T) {
 		wantErr = &ParseError{Kind: KindUnsupportedFormat}
 	)
 	h := make(http.Header)
-	h.Set(http.CanonicalHeaderKey("Content-Type"), contentType)
+	h.Set(headerCT, contentType)
 
 	RegisterBodyDecoder(contentType, decoder)
 	got, err := decodeBody(body, h, schema, encFn)
diff --git a/openapi3filter/router_test.go b/openapi3filter/router_test.go
index 5a72411..6e8f7fd 100644
--- a/openapi3filter/router_test.go
+++ b/openapi3filter/router_test.go
@@ -1,23 +1,16 @@
-package openapi3filter_test
+package openapi3filter
 
 import (
+	"context"
 	"net/http"
 	"sort"
 	"testing"
 
 	"github.com/getkin/kin-openapi/openapi3"
-	"github.com/getkin/kin-openapi/openapi3filter"
 	"github.com/stretchr/testify/require"
 )
 
 func TestRouter(t *testing.T) {
-	var (
-		pathNotFound          = "Path was not found"
-		methodNotAllowed      = "Path doesn't support the HTTP method"
-		doesNotMatchAnyServer = "Does not match any server"
-	)
-
-	// Build swagger
 	helloCONNECT := &openapi3.Operation{Responses: openapi3.NewResponses()}
 	helloDELETE := &openapi3.Operation{Responses: openapi3.NewResponses()}
 	helloGET := &openapi3.Operation{Responses: openapi3.NewResponses()}
@@ -28,6 +21,7 @@ func TestRouter(t *testing.T) {
 	helloPUT := &openapi3.Operation{Responses: openapi3.NewResponses()}
 	helloTRACE := &openapi3.Operation{Responses: openapi3.NewResponses()}
 	paramsGET := &openapi3.Operation{Responses: openapi3.NewResponses()}
+	booksPOST := &openapi3.Operation{Responses: openapi3.NewResponses()}
 	partialGET := &openapi3.Operation{Responses: openapi3.NewResponses()}
 	swagger := &openapi3.Swagger{
 		OpenAPI: "3.0.0",
@@ -50,6 +44,7 @@ func TestRouter(t *testing.T) {
 			"/onlyGET": &openapi3.PathItem{
 				Get: helloGET,
 			},
+			//TODO: use "/params/{x}/{y}/{z:.*}": &openapi3.PathItem{ when reworking https://github.com/getkin/kin-openapi/pull/210
 			"/params/{x}/{y}/{z*}": &openapi3.PathItem{
 				Get: paramsGET,
 				Parameters: openapi3.Parameters{
@@ -58,48 +53,48 @@ func TestRouter(t *testing.T) {
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("z")},
 				},
 			},
+			"/books/{bookid}": &openapi3.PathItem{
+				Get: paramsGET,
+				Parameters: openapi3.Parameters{
+					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid")},
+				},
+			},
+			"/books/{bookid2}.json": &openapi3.PathItem{
+				Post: booksPOST,
+				Parameters: openapi3.Parameters{
+					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid2")},
+				},
+			},
 			"/partial": &openapi3.PathItem{
 				Get: partialGET,
 			},
 		},
 	}
 
-	// Build router
-	router := openapi3filter.NewRouter().WithSwagger(swagger)
-
-	// Declare a helper function
-	expect := func(method string, uri string, operation *openapi3.Operation, params map[string]string) {
+	expect := func(r *Router, method string, uri string, operation *openapi3.Operation, params map[string]string) {
 		req, err := http.NewRequest(method, uri, nil)
 		require.NoError(t, err)
-		route, pathParams, err := router.FindRoute(req.Method, req.URL)
+		route, pathParams, err := r.FindRoute(req.Method, req.URL)
 		if err != nil {
 			if operation == nil {
-				if err.Error() == doesNotMatchAnyServer {
-					return
-				}
-
 				pathItem := swagger.Paths[uri]
 				if pathItem == nil {
-					if err.Error() != pathNotFound {
-						t.Fatalf("'%s %s': should have returned '%s', but it returned an error: %v",
-							method, uri, pathNotFound, err)
+					if err.Error() != ErrPathNotFound.Error() {
+						t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, ErrPathNotFound, err)
 					}
 					return
 				}
 				if pathItem.GetOperation(method) == nil {
-					if err.Error() != methodNotAllowed {
-						t.Fatalf("'%s %s': should have returned '%s', but it returned an error: %v",
-							method, uri, methodNotAllowed, err)
+					if err.Error() != ErrMethodNotAllowed.Error() {
+						t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, ErrMethodNotAllowed, err)
 					}
 				}
 			} else {
-				t.Fatalf("'%s %s': should have returned an operation, but it returned an error: %v",
-					method, uri, err)
+				t.Fatalf("'%s %s': should have returned an operation, but it returned an error: %v", method, uri, err)
 			}
 		}
 		if operation == nil && err == nil {
-			t.Fatalf("'%s %s': should have returned an error, but didn't",
-				method, uri)
+			t.Fatalf("'%s %s': should have failed, but returned\nroute = %+v\npathParams = %+v", method, uri, route, pathParams)
 		}
 		if route == nil {
 			return
@@ -108,10 +103,9 @@ func TestRouter(t *testing.T) {
 			t.Fatalf("'%s %s': Returned wrong operation (%v)",
 				method, uri, route.Operation)
 		}
-		if params == nil {
+		if len(params) == 0 {
 			if len(pathParams) != 0 {
-				t.Fatalf("'%s %s': should return no path arguments, but found some",
-					method, uri)
+				t.Fatalf("'%s %s': should return no path arguments, but found %+v", method, uri, pathParams)
 			}
 		} else {
 			names := make([]string, 0, len(params))
@@ -123,54 +117,71 @@ func TestRouter(t *testing.T) {
 				expected := params[name]
 				actual, exists := pathParams[name]
 				if !exists {
-					t.Fatalf("'%s %s': path parameter '%s' should be '%s', but it's not defined.",
-						method, uri, name, expected)
+					t.Fatalf("'%s %s': path parameter %q should be %q, but it's not defined.", method, uri, name, expected)
 				}
 				if actual != expected {
-					t.Fatalf("'%s %s': path parameter '%s' should be '%s', but it's '%s'",
-						method, uri, name, expected, actual)
+					t.Fatalf("'%s %s': path parameter %q should be %q, but it's %q", method, uri, name, expected, actual)
 				}
 			}
 		}
 	}
-	expect(http.MethodGet, "/not_existing", nil, nil)
-	expect(http.MethodDelete, "/hello", helloDELETE, nil)
-	expect(http.MethodGet, "/hello", helloGET, nil)
-	expect(http.MethodHead, "/hello", helloHEAD, nil)
-	expect(http.MethodPatch, "/hello", helloPATCH, nil)
-	expect(http.MethodPost, "/hello", helloPOST, nil)
-	expect(http.MethodPut, "/hello", helloPUT, nil)
-	expect(http.MethodGet, "/params/a/b/c/d", paramsGET, map[string]string{
+
+	err := swagger.Validate(context.Background())
+	require.NoError(t, err)
+	r := NewRouter().WithSwagger(swagger)
+
+	expect(r, http.MethodGet, "/not_existing", nil, nil)
+	expect(r, http.MethodDelete, "/hello", helloDELETE, nil)
+	expect(r, http.MethodGet, "/hello", helloGET, nil)
+	expect(r, http.MethodHead, "/hello", helloHEAD, nil)
+	expect(r, http.MethodPatch, "/hello", helloPATCH, nil)
+	expect(r, http.MethodPost, "/hello", helloPOST, nil)
+	expect(r, http.MethodPut, "/hello", helloPUT, nil)
+	expect(r, http.MethodGet, "/params/a/b/", paramsGET, map[string]string{
+		"x": "a",
+		"y": "b",
+		"z": "",
+	})
+	expect(r, http.MethodGet, "/params/a/b/c%2Fd", paramsGET, map[string]string{
 		"x": "a",
 		"y": "b",
 		"z": "c/d",
 	})
-	expect(http.MethodPost, "/partial", nil, nil)
-	swagger.Servers = append(swagger.Servers, &openapi3.Server{
-		URL: "https://www.example.com/api/v1/",
-	}, &openapi3.Server{
-		URL: "https://{d0}.{d1}.com/api/v1/",
+	expect(r, http.MethodGet, "/books/War.and.Peace", paramsGET, map[string]string{
+		"bookid": "War.and.Peace",
 	})
-	expect(http.MethodGet, "/hello", nil, nil)
-	expect(http.MethodGet, "/api/v1/hello", nil, nil)
-	expect(http.MethodGet, "www.example.com/api/v1/hello", nil, nil)
-	expect(http.MethodGet, "https:///api/v1/hello", nil, nil)
-	expect(http.MethodGet, "https://www.example.com/hello", nil, nil)
-	expect(http.MethodGet, "https://www.example.com/api/v1/hello", helloGET, map[string]string{})
-	expect(http.MethodGet, "https://domain0.domain1.com/api/v1/hello", helloGET, map[string]string{
+	// TODO: fix https://github.com/getkin/kin-openapi/issues/129
+	// expect(r, http.MethodPost, "/books/War.and.Peace.json", booksPOST, map[string]string{
+	// 	"bookid2": "War.and.Peace",
+	// })
+	expect(r, http.MethodPost, "/partial", nil, nil)
+
+	swagger.Servers = []*openapi3.Server{
+		{URL: "https://www.example.com/api/v1"},
+		{URL: "https://{d0}.{d1}.com/api/v1/"},
+	}
+	err = swagger.Validate(context.Background())
+	require.NoError(t, err)
+	r = NewRouter().WithSwagger(swagger)
+	expect(r, http.MethodGet, "/hello", nil, nil)
+	expect(r, http.MethodGet, "/api/v1/hello", nil, nil)
+	expect(r, http.MethodGet, "www.example.com/api/v1/hello", nil, nil)
+	expect(r, http.MethodGet, "https:///api/v1/hello", nil, nil)
+	expect(r, http.MethodGet, "https://www.example.com/hello", nil, nil)
+	expect(r, http.MethodGet, "https://www.example.com/api/v1/hello", helloGET, nil)
+	expect(r, http.MethodGet, "https://domain0.domain1.com/api/v1/hello", helloGET, map[string]string{
 		"d0": "domain0",
 		"d1": "domain1",
 	})
 
 	{
 		uri := "https://www.example.com/api/v1/onlyGET"
-		expect(http.MethodGet, uri, helloGET, nil)
+		expect(r, http.MethodGet, uri, helloGET, nil)
 		req, err := http.NewRequest(http.MethodDelete, uri, nil)
 		require.NoError(t, err)
 		require.NotNil(t, req)
-		route, pathParams, err := router.FindRoute(req.Method, req.URL)
-		require.Error(t, err)
-		require.Equal(t, err.(*openapi3filter.RouteError).Reason, "Path doesn't support the HTTP method")
+		route, pathParams, err := r.FindRoute(req.Method, req.URL)
+		require.EqualError(t, err, ErrMethodNotAllowed.Error())
 		require.Nil(t, route)
 		require.Nil(t, pathParams)
 	}
diff --git a/openapi3filter/validate_readonly_test.go b/openapi3filter/validate_readonly_test.go
index fac9bf5..6812c69 100644
--- a/openapi3filter/validate_readonly_test.go
+++ b/openapi3filter/validate_readonly_test.go
@@ -65,16 +65,18 @@ func TestValidatingRequestBodyWithReadOnlyProperty(t *testing.T) {
 	}
 
 	sl := openapi3.NewSwaggerLoader()
-	l, err := sl.LoadSwaggerFromData([]byte(spec))
+	doc, err := sl.LoadSwaggerFromData([]byte(spec))
 	require.NoError(t, err)
-	router := NewRouter().WithSwagger(l)
+	err = doc.Validate(sl.Context)
+	require.NoError(t, err)
+	router := NewRouter().WithSwagger(doc)
 
 	b, err := json.Marshal(Request{ID: "bt6kdc3d0cvp6u8u3ft0"})
 	require.NoError(t, err)
 
 	httpReq, err := http.NewRequest(http.MethodPost, "/accounts", bytes.NewReader(b))
 	require.NoError(t, err)
-	httpReq.Header.Add("Content-Type", "application/json")
+	httpReq.Header.Add(headerCT, "application/json")
 
 	route, pathParams, err := router.FindRoute(httpReq.Method, httpReq.URL)
 	require.NoError(t, err)
diff --git a/openapi3filter/validate_request_test.go b/openapi3filter/validate_request_test.go
index ba320ca..e18d0f4 100644
--- a/openapi3filter/validate_request_test.go
+++ b/openapi3filter/validate_request_test.go
@@ -99,10 +99,30 @@ func Example() {
 		PathParams: pathParams,
 		Route:      route,
 	}
-	if err = openapi3filter.ValidateRequest(loader.Context, requestValidationInput); err == nil {
-		fmt.Println("Valid")
-	} else {
-		fmt.Println("NOT valid")
+	if err := openapi3filter.ValidateRequest(loader.Context, requestValidationInput); err != nil {
+		fmt.Println(err)
 	}
-	// Output: NOT valid
+	// Output:
+	// request body has an error: doesn't match the schema: Doesn't match schema "oneOf"
+	// Schema:
+	//   {
+	//     "discriminator": {
+	//       "propertyName": "pet_type"
+	//     },
+	//     "oneOf": [
+	//       {
+	//         "$ref": "#/components/schemas/Cat"
+	//       },
+	//       {
+	//         "$ref": "#/components/schemas/Dog"
+	//       }
+	//     ]
+	//   }
+	//
+	// Value:
+	//   {
+	//     "bark": true,
+	//     "breed": "Dingo",
+	//     "pet_type": "Cat"
+	//   }
 }
diff --git a/openapi3filter/validation_discriminator_test.go b/openapi3filter/validation_discriminator_test.go
index 51d804f..7929447 100644
--- a/openapi3filter/validation_discriminator_test.go
+++ b/openapi3filter/validation_discriminator_test.go
@@ -64,7 +64,7 @@ components:
         properties:
           base64:
             type: string
-      
+
     objB:
       allOf:
       - $ref: '#/components/schemas/genericObj'
@@ -77,7 +77,7 @@ components:
 func forgeRequest(body string) *http.Request {
 	iobody := bytes.NewReader([]byte(body))
 	req, _ := http.NewRequest("PUT", "/blob", iobody)
-	req.Header.Add("Content-Type", "application/json")
+	req.Header.Add(headerCT, "application/json")
 	return req
 }
 
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index 0a28ab6..f6e5de2 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -19,7 +19,7 @@ func newPetstoreRequest(t *testing.T, method, path string, body io.Reader) *http
 	pathPrefix := "v2"
 	r, err := http.NewRequest(method, fmt.Sprintf("http://%s/%s%s", host, pathPrefix, path), body)
 	require.NoError(t, err)
-	r.Header.Set("Content-Type", "application/json")
+	r.Header.Set(headerCT, "application/json")
 	r.Header.Set("Authorization", "Bearer magicstring")
 	r.Host = host
 	return r
@@ -63,16 +63,16 @@ func getValidationTests(t *testing.T) []*validationTest {
 	missingBody2 := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(``))
 
 	noContentType := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
-	noContentType.Header.Del("Content-Type")
+	noContentType.Header.Del(headerCT)
 
 	noContentTypeNeeded := newPetstoreRequest(t, http.MethodGet, "/pet/findByStatus?status=sold", nil)
-	noContentTypeNeeded.Header.Del("Content-Type")
+	noContentTypeNeeded.Header.Del(headerCT)
 
 	unknownContentType := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
-	unknownContentType.Header.Set("Content-Type", "application/xml")
+	unknownContentType.Header.Set(headerCT, "application/xml")
 
 	unsupportedContentType := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
-	unsupportedContentType.Header.Set("Content-Type", "text/plain")
+	unsupportedContentType.Header.Set(headerCT, "text/plain")
 
 	unsupportedHeaderValue := newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{}`))
 	unsupportedHeaderValue.Header.Set("x-environment", "watdis")
@@ -87,45 +87,45 @@ func getValidationTests(t *testing.T) []*validationTest {
 			args: validationArgs{
 				r: badHost,
 			},
-			wantErrReason:   "Does not match any server",
-			wantErrResponse: &ValidationError{Status: http.StatusNotFound, Title: "Does not match any server"},
+			wantErrReason:   ErrPathNotFound.Error(),
+			wantErrResponse: &ValidationError{Status: http.StatusNotFound, Title: ErrPathNotFound.Error()},
 		},
 		{
 			name: "error - unknown path",
 			args: validationArgs{
 				r: badPath,
 			},
-			wantErrReason:   "Path was not found",
-			wantErrResponse: &ValidationError{Status: http.StatusNotFound, Title: "Path was not found"},
+			wantErrReason:   ErrPathNotFound.Error(),
+			wantErrResponse: &ValidationError{Status: http.StatusNotFound, Title: ErrPathNotFound.Error()},
 		},
 		{
 			name: "error - unknown method",
 			args: validationArgs{
 				r: badMethod,
 			},
-			wantErrReason: "Path doesn't support the HTTP method",
+			wantErrReason: ErrMethodNotAllowed.Error(),
 			// TODO: By HTTP spec, this should have an Allow header with what is allowed
 			// but kin-openapi doesn't provide us the requested method or path, so impossible to provide details
 			wantErrResponse: &ValidationError{Status: http.StatusMethodNotAllowed,
-				Title: "Path doesn't support the HTTP method"},
+				Title: ErrMethodNotAllowed.Error()},
 		},
 		{
 			name: "error - missing body on POST",
 			args: validationArgs{
 				r: missingBody1,
 			},
-			wantErrBody: "Request body has an error: must have a value",
+			wantErrBody: "request body has an error: " + ErrInvalidRequired.Error(),
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "Request body has an error: must have a value"},
+				Title: "request body has an error: " + ErrInvalidRequired.Error()},
 		},
 		{
 			name: "error - empty body on POST",
 			args: validationArgs{
 				r: missingBody2,
 			},
-			wantErrBody: "Request body has an error: must have a value",
+			wantErrBody: "request body has an error: " + ErrInvalidRequired.Error(),
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "Request body has an error: must have a value"},
+				Title: "request body has an error: " + ErrInvalidRequired.Error()},
 		},
 
 		//
@@ -137,9 +137,9 @@ func getValidationTests(t *testing.T) []*validationTest {
 			args: validationArgs{
 				r: noContentType,
 			},
-			wantErrReason: "header 'Content-Type' has unexpected value: \"\"",
+			wantErrReason: prefixInvalidCT + ` ""`,
 			wantErrResponse: &ValidationError{Status: http.StatusUnsupportedMediaType,
-				Title: "header 'Content-Type' is required"},
+				Title: "header Content-Type is required"},
 		},
 		{
 			name: "error - unknown content-type on POST",
@@ -148,18 +148,18 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrReason:      "failed to decode request body",
 			wantErrParseKind:   KindUnsupportedFormat,
-			wantErrParseReason: "unsupported content type \"application/xml\"",
+			wantErrParseReason: prefixUnsupportedCT + ` "application/xml"`,
 			wantErrResponse: &ValidationError{Status: http.StatusUnsupportedMediaType,
-				Title: "unsupported content type \"application/xml\""},
+				Title: prefixUnsupportedCT + ` "application/xml"`},
 		},
 		{
 			name: "error - unsupported content-type on POST",
 			args: validationArgs{
 				r: unsupportedContentType,
 			},
-			wantErrReason: "header 'Content-Type' has unexpected value: \"text/plain\"",
+			wantErrReason: prefixInvalidCT + ` "text/plain"`,
 			wantErrResponse: &ValidationError{Status: http.StatusUnsupportedMediaType,
-				Title: "unsupported content type \"text/plain\""},
+				Title: prefixUnsupportedCT + ` "text/plain"`},
 		},
 		{
 			name: "success - no content-type header required on GET",
@@ -179,9 +179,9 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:   "status",
 			wantErrParamIn: "query",
-			wantErrReason:  "must have a value",
+			wantErrReason:  ErrInvalidRequired.Error(),
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "Parameter 'status' in query is required"},
+				Title: `parameter "status" in query is required`},
 		},
 		{
 			name: "error - wrong query string parameter type",
@@ -192,11 +192,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			wantErrParamIn: "query",
 			// This is a nested ParseError. The outer error is a KindOther with no details.
 			// So we'd need to look at the inner one which is a KindInvalidFormat. So just check the error body.
-			wantErrBody: "Parameter 'ids' in query has an error: path 1: value notAnInt: an invalid integer: " +
+			wantErrBody: `parameter "ids" in query has an error: path 1: value notAnInt: an invalid integer: ` +
 				"strconv.ParseFloat: parsing \"notAnInt\": invalid syntax",
 			// TODO: Should we treat query params of the wrong type like a 404 instead of a 400?
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "Parameter 'ids' in query is invalid: notAnInt is an invalid integer"},
+				Title: `parameter "ids" in query is invalid: notAnInt is an invalid integer`},
 		},
 		{
 			name: "success - ignores unknown query string parameter",
@@ -223,12 +223,12 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "JSON value is not one of the allowed values",
+			wantErrSchemaReason: "value is not one of the allowed values",
 			wantErrSchemaPath:   "/0",
 			wantErrSchemaValue:  "available,sold",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "JSON value is not one of the allowed values",
-				Detail: "Value 'available,sold' at /0 must be one of: available, pending, sold; " +
+				Title: "value 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?
 					"perhaps you intended '?status=available&status=sold'",
@@ -241,12 +241,12 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "JSON value is not one of the allowed values",
+			wantErrSchemaReason: "value is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "JSON value is not one of the allowed values",
-				Detail: "Value 'watdis' at /1 must be one of: available, pending, sold",
+				Title:  "value is not one of the allowed values",
+				Detail: "value watdis at /1 must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Parameter: "status"}},
 		},
 		{
@@ -257,12 +257,12 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "kind",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "JSON value is not one of the allowed values",
+			wantErrSchemaReason: "value is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "fish,with,commas",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "JSON value is not one of the allowed values",
-				Detail: "Value 'fish,with,commas' at /1 must be one of: dog, cat, turtle, bird,with,commas",
+				Title:  "value 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"}},
 		},
@@ -283,12 +283,12 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "x-environment",
 			wantErrParamIn:      "header",
-			wantErrSchemaReason: "JSON value is not one of the allowed values",
+			wantErrSchemaReason: "value is not one of the allowed values",
 			wantErrSchemaPath:   "/",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "JSON value is not one of the allowed values",
-				Detail: "Value 'watdis' at / must be one of: demo, prod",
+				Title:  "value is not one of the allowed values",
+				Detail: "value watdis at / must be one of: demo, prod",
 				Source: &ValidationErrorSource{Parameter: "x-environment"}},
 		},
 
@@ -302,12 +302,12 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "JSON value is not one of the allowed values",
+			wantErrSchemaReason: "value is not one of the allowed values",
 			wantErrSchemaValue:  "watdis",
 			wantErrSchemaPath:   "/status",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "JSON value is not one of the allowed values",
-				Detail: "Value 'watdis' at /status must be one of: available, pending, sold",
+				Title:  "value is not one of the allowed values",
+				Detail: "value watdis at /status must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Pointer: "/status"}},
 		},
 		{
@@ -316,11 +316,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"name":"Bahama"}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "Property 'photoUrls' is missing",
+			wantErrSchemaReason: `property "photoUrls" is missing`,
 			wantErrSchemaValue:  map[string]string{"name": "Bahama"},
 			wantErrSchemaPath:   "/photoUrls",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "Property 'photoUrls' is missing",
+				Title:  `property "photoUrls" is missing`,
 				Source: &ValidationErrorSource{Pointer: "/photoUrls"}},
 		},
 		{
@@ -330,11 +330,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{}}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "Property 'name' is missing",
+			wantErrSchemaReason: `property "name" is missing`,
 			wantErrSchemaValue:  map[string]string{},
 			wantErrSchemaPath:   "/category/name",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "Property 'name' is missing",
+				Title:  `property "name" is missing`,
 				Source: &ValidationErrorSource{Pointer: "/category/name"}},
 		},
 		{
@@ -344,11 +344,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{"tags": [{}]}}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "Property 'name' is missing",
+			wantErrSchemaReason: `property "name" is missing`,
 			wantErrSchemaValue:  map[string]string{},
 			wantErrSchemaPath:   "/category/tags/0/name",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "Property 'name' is missing",
+				Title:  `property "name" is missing`,
 				Source: &ValidationErrorSource{Pointer: "/category/tags/0/name"}},
 		},
 		{
@@ -384,11 +384,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			wantErrReason:             "doesn't match the schema",
 			wantErrSchemaPath:         "/",
 			wantErrSchemaValue:        map[string]string{"name": "Bahama"},
-			wantErrSchemaOriginReason: "Property 'photoUrls' is missing",
+			wantErrSchemaOriginReason: `property "photoUrls" is missing`,
 			wantErrSchemaOriginValue:  map[string]string{"name": "Bahama"},
 			wantErrSchemaOriginPath:   "/photoUrls",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "Property 'photoUrls' is missing",
+				Title:  `property "photoUrls" is missing`,
 				Source: &ValidationErrorSource{Pointer: "/photoUrls"}},
 		},
 		{
@@ -423,9 +423,9 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:   "petId",
 			wantErrParamIn: "path",
-			wantErrReason:  "must have a value",
+			wantErrReason:  ErrInvalidRequired.Error(),
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "Parameter 'petId' in path is required"},
+				Title: `parameter "petId" in path is required`},
 		},
 		{
 			name: "error - wrong path param type",
@@ -438,7 +438,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 			wantErrParseValue:  "NotAnInt",
 			wantErrParseReason: "an invalid integer",
 			wantErrResponse: &ValidationError{Status: http.StatusNotFound,
-				Title: "Resource not found with 'petId' value: NotAnInt"},
+				Title: `resource not found with "petId" value: NotAnInt`},
 		},
 		{
 			name: "success - normal case, with path params",
diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go
index 4de536e..10d7a38 100644
--- a/openapi3filter/validation_test.go
+++ b/openapi3filter/validation_test.go
@@ -103,7 +103,7 @@ func TestFilter(t *testing.T) {
 								).NewRef(),
 							},
 						},
-						// TODO(decode not): handle decoding "not" JSON Schema
+						// TODO(decode not): handle decoding "not" Schema
 						// {
 						// 	Value: &openapi3.Parameter{
 						// 		In:   "query",
@@ -154,11 +154,13 @@ func TestFilter(t *testing.T) {
 		},
 	}
 
+	err := swagger.Validate(context.Background())
+	require.NoError(t, err)
 	router := NewRouter().WithSwagger(swagger)
 	expectWithDecoder := func(req ExampleRequest, resp ExampleResponse, decoder ContentParameterDecoder) error {
 		t.Logf("Request: %s %s", req.Method, req.URL)
 		httpReq, _ := http.NewRequest(req.Method, req.URL, marshalReader(req.Body))
-		httpReq.Header.Set("Content-Type", req.ContentType)
+		httpReq.Header.Set(headerCT, req.ContentType)
 
 		// Find route
 		route, pathParams, err := router.FindRoute(httpReq.Method, httpReq.URL)
@@ -179,7 +181,7 @@ func TestFilter(t *testing.T) {
 			RequestValidationInput: requestValidationInput,
 			Status:                 resp.Status,
 			Header: http.Header{
-				"Content-Type": []string{
+				headerCT: []string{
 					resp.ContentType,
 				},
 			},
@@ -197,15 +199,12 @@ func TestFilter(t *testing.T) {
 		return expectWithDecoder(req, resp, nil)
 	}
 
-	var err error
-	var req ExampleRequest
-	var resp ExampleResponse
-	resp = ExampleResponse{
+	resp := ExampleResponse{
 		Status: 200,
 	}
 	// Test paths
 
-	req = ExampleRequest{
+	req := ExampleRequest{
 		Method: "POST",
 		URL:    "http://example.com/api/prefix/v/suffix",
 	}
@@ -286,7 +285,7 @@ func TestFilter(t *testing.T) {
 	err = expect(req, resp)
 	require.IsType(t, &RequestError{}, err)
 
-	// TODO(decode not): handle decoding "not" JSON Schema
+	// TODO(decode not): handle decoding "not" Schema
 	// req = ExampleRequest{
 	// 	Method: "POST",
 	// 	URL:    "http://example.com/api/prefix/v/suffix?queryArgNot=abdfg",
@@ -294,7 +293,7 @@ func TestFilter(t *testing.T) {
 	// err = expect(req, resp)
 	// require.IsType(t, &RequestError{}, err)
 
-	// TODO(decode not): handle decoding "not" JSON Schema
+	// TODO(decode not): handle decoding "not" Schema
 	// req = ExampleRequest{
 	// 	Method: "POST",
 	// 	URL:    "http://example.com/api/prefix/v/suffix?queryArgNot=123",
@@ -433,7 +432,7 @@ func TestValidateRequestBody(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			req := httptest.NewRequest(http.MethodPost, "/test", tc.data)
 			if tc.mime != "" {
-				req.Header.Set(http.CanonicalHeaderKey("Content-Type"), tc.mime)
+				req.Header.Set(headerCT, tc.mime)
 			}
 			inp := &RequestValidationInput{Request: req}
 			err := ValidateRequestBody(context.Background(), inp, tc.body)
@@ -570,6 +569,8 @@ func TestOperationOrSwaggerSecurity(t *testing.T) {
 		}
 	}
 
+	err := swagger.Validate(context.Background())
+	require.NoError(t, err)
 	// Declare the router
 	router := NewRouter().WithSwagger(swagger)
 
@@ -701,6 +702,8 @@ func TestAnySecurityRequirementMet(t *testing.T) {
 		}
 	}
 
+	err := swagger.Validate(context.Background())
+	require.NoError(t, err)
 	// Create the router
 	router := NewRouter().WithSwagger(&swagger)
 
@@ -801,6 +804,8 @@ func TestAllSchemesMet(t *testing.T) {
 		}
 	}
 
+	err := swagger.Validate(context.Background())
+	require.NoError(t, err)
 	// Create the router from the swagger
 	router := NewRouter().WithSwagger(&swagger)
 
diff --git a/pathpattern/node_test.go b/pathpattern/node_test.go
index 6d0ec9d..f484239 100644
--- a/pathpattern/node_test.go
+++ b/pathpattern/node_test.go
@@ -36,11 +36,11 @@ func TestPatterns(t *testing.T) {
 			}
 		}
 		if actually != expected {
-			t.Fatalf("Wrong path!\nInput: %s\nExpected: '%s'\nActually: '%s'\nTree:\n%s\n\n", uri, expected, actually, rootNode.String())
+			t.Fatalf("Wrong path!\nInput: %s\nExpected: %q\nActually: %q\nTree:\n%s\n\n", uri, expected, actually, rootNode.String())
 			return
 		}
 		if !argsEqual(expectedArgs, actualArgs) {
-			t.Fatalf("Wrong variable values!\nInput: %s\nExpected: '%s'\nActually: '%s'\nTree:\n%s\n\n", uri, expectedArgs, actualArgs, rootNode.String())
+			t.Fatalf("Wrong variable values!\nInput: %s\nExpected: %q\nActually: %q\nTree:\n%s\n\n", uri, expectedArgs, actualArgs, rootNode.String())
 			return
 		}
 	}