Repository: getkin/kin-openapi
Pull request #684 (2022-11-28 12:31:28)

feat: formatting some error messages
This commits adds some clarity to the end users about what it is failing on their end, adding values does not match, value X is not part of the enum, etc.. So for end-users, it's a bit easier to understand.

Signed-off-by: Eloy Coto <eloy.coto@acalustra.com>


Patch:
diff --git a/openapi3/schema.go b/openapi3/schema.go
index eb01673..081873e 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -883,7 +883,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			Value:                 value,
 			Schema:                schema,
 			SchemaField:           "enum",
-			Reason:                "value is not one of the allowed values",
+			Reason:                fmt.Sprintf("value %q is not one of the allowed values", value),
 			customizeMessageError: settings.customizeMessageError,
 		}
 	}
@@ -1085,7 +1085,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				Value:                 value,
 				Schema:                schema,
 				SchemaField:           "type",
-				Reason:                "Value must be an integer",
+				Reason:                fmt.Sprintf("value \"%g\" must be an integer", value),
 				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
@@ -1305,7 +1305,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 			Value:                 value,
 			Schema:                schema,
 			SchemaField:           "pattern",
-			Reason:                fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
+			Reason:                fmt.Sprintf(`string %q doesn't match the regular expression "%s"`, value, schema.Pattern),
 			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
@@ -1650,7 +1650,7 @@ func (schema *Schema) expectedType(settings *schemaValidationSettings, typ strin
 		Value:                 typ,
 		Schema:                schema,
 		SchemaField:           "type",
-		Reason:                "Field must be set to " + schema.Type + " or not be present",
+		Reason:                fmt.Sprintf("field must be set to %s or not be present", schema.Type),
 		customizeMessageError: settings.customizeMessageError,
 	}
 }


Test patch:
diff --git a/openapi3/issue136_test.go b/openapi3/issue136_test.go
index 36c8b85..b5e9eeb 100644
--- a/openapi3/issue136_test.go
+++ b/openapi3/issue136_test.go
@@ -31,7 +31,7 @@ components:
 		},
 		{
 			dflt: `1`,
-			err:  "invalid components: invalid schema default: Field must be set to string or not be present",
+			err:  "invalid components: invalid schema default: field must be set to string or not be present",
 		},
 	} {
 		t.Run(testcase.dflt, func(t *testing.T) {
diff --git a/openapi3filter/issue201_test.go b/openapi3filter/issue201_test.go
index 8db8620..8b2b99d 100644
--- a/openapi3filter/issue201_test.go
+++ b/openapi3filter/issue201_test.go
@@ -94,7 +94,7 @@ paths:
 		},
 
 		"invalid required header": {
-			err: `response header "X-Blup" doesn't match the schema: string doesn't match the regular expression "^blup$"`,
+			err: `response header "X-Blup" doesn't match the schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
 			headers: map[string]string{
 				"X-Blip": "blip",
 				"x-blop": "blop",
diff --git a/openapi3filter/issue625_test.go b/openapi3filter/issue625_test.go
index f071343..d9e5bae 100644
--- a/openapi3filter/issue625_test.go
+++ b/openapi3filter/issue625_test.go
@@ -72,7 +72,7 @@ paths:
 			name:   "failed allof object array",
 			spec:   allOfArraySpec,
 			req:    `/items?test=1.2,3.1`,
-			errStr: `parameter "test" in query has an error: Error at "/0": Value must be an integer`,
+			errStr: `parameter "test" in query has an error: Error at "/0": value "1.2" must be an integer`,
 		},
 		{
 			name: "success oneof object array",
diff --git a/openapi3filter/unpack_errors_test.go b/openapi3filter/unpack_errors_test.go
index 552e20e..0ee48ad 100644
--- a/openapi3filter/unpack_errors_test.go
+++ b/openapi3filter/unpack_errors_test.go
@@ -81,7 +81,7 @@ func Example() {
 	// Output:
 	// ===== Start New Error =====
 	// @body.name:
-	// 	Error at "/name": Field must be set to string or not be present
+	// 	Error at "/name": field must be set to string or not be present
 	// Schema:
 	//   {
 	//     "example": "doggie",
@@ -93,7 +93,7 @@ func Example() {
 	//
 	// ===== Start New Error =====
 	// @body.status:
-	// 	Error at "/status": value is not one of the allowed values
+	// 	Error at "/status": value "invalidStatus" is not one of the allowed values
 	// Schema:
 	//   {
 	//     "description": "pet status in the store",
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index c1d4630..6fee135 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -244,11 +244,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"available,sold\" is not one of the allowed values",
 			wantErrSchemaPath:   "/0",
 			wantErrSchemaValue:  "available,sold",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title: "value is not one of the allowed values",
+				Title: "value \"available,sold\" is not one of the allowed values",
 				Detail: "value available,sold at /0 must be one of: available, pending, sold; " +
 					// TODO: do we really want to use this heuristic to guess
 					//  that they're using the wrong serialization?
@@ -262,11 +262,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "status",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				Detail: "value watdis at /1 must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Parameter: "status"}},
 		},
@@ -278,11 +278,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "kind",
 			wantErrParamIn:      "query",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"fish,with,commas\" is not one of the allowed values",
 			wantErrSchemaPath:   "/1",
 			wantErrSchemaValue:  "fish,with,commas",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"fish,with,commas\" is not one of the allowed values",
 				Detail: "value fish,with,commas at /1 must be one of: dog, cat, turtle, bird,with,commas",
 				// No 'perhaps you intended' because its the right serialization format
 				Source: &ValidationErrorSource{Parameter: "kind"}},
@@ -304,11 +304,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 			},
 			wantErrParam:        "x-environment",
 			wantErrParamIn:      "header",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaPath:   "/",
 			wantErrSchemaValue:  "watdis",
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				Detail: "value watdis at / must be one of: demo, prod",
 				Source: &ValidationErrorSource{Parameter: "x-environment"}},
 		},
@@ -323,11 +323,11 @@ func getValidationTests(t *testing.T) []*validationTest {
 				r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "value is not one of the allowed values",
+			wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
 			wantErrSchemaValue:  "watdis",
 			wantErrSchemaPath:   "/status",
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "value is not one of the allowed values",
+				Title:  "value \"watdis\" is not one of the allowed values",
 				Detail: "value watdis at /status must be one of: available, pending, sold",
 				Source: &ValidationErrorSource{Pointer: "/status"}},
 		},
@@ -379,13 +379,13 @@ func getValidationTests(t *testing.T) []*validationTest {
 					bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`)),
 			},
 			wantErrReason:       "doesn't match the schema",
-			wantErrSchemaReason: "Field must be set to array or not be present",
+			wantErrSchemaReason: "field must be set to array or not be present",
 			wantErrSchemaPath:   "/photoUrls",
 			wantErrSchemaValue:  "string",
 			// TODO: this shouldn't say "or not be present", but this requires recursively resolving
 			//  innerErr.JSONPointer() against e.RequestBody.Content["application/json"].Schema.Value (.Required, .Properties)
 			wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
-				Title:  "Field must be set to array or not be present",
+				Title:  "field must be set to array or not be present",
 				Source: &ValidationErrorSource{Pointer: "/photoUrls"}},
 		},
 		{
@@ -659,7 +659,7 @@ func TestValidationHandler_ServeHTTP(t *testing.T) {
 		body, err := ioutil.ReadAll(resp.Body)
 		require.NoError(t, err)
 		require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
-		require.Equal(t, "[422][][] Field must be set to array or not be present [source pointer=/photoUrls]", string(body))
+		require.Equal(t, "[422][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
 	})
 }
 
@@ -701,6 +701,6 @@ func TestValidationHandler_Middleware(t *testing.T) {
 		body, err := ioutil.ReadAll(resp.Body)
 		require.NoError(t, err)
 		require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
-		require.Equal(t, "[422][][] Field must be set to array or not be present [source pointer=/photoUrls]", string(body))
+		require.Equal(t, "[422][][] field must be set to array or not be present [source pointer=/photoUrls]", string(body))
 	})
 }
diff --git a/routers/gorillamux/example_test.go b/routers/gorillamux/example_test.go
index 33ce98e..2ca3225 100644
--- a/routers/gorillamux/example_test.go
+++ b/routers/gorillamux/example_test.go
@@ -53,7 +53,7 @@ func Example() {
 	err = openapi3filter.ValidateResponse(ctx, responseValidationInput)
 	fmt.Println(err)
 	// Output:
-	// response body doesn't match the schema: Field must be set to string or not be present
+	// response body doesn't match the schema: field must be set to string or not be present
 	// Schema:
 	//   {
 	//     "type": "string"