Skip to content

Commit

Permalink
openapi3: remove value data from SchemaError.Reason field (#737)
Browse files Browse the repository at this point in the history
Resolves #735
  • Loading branch information
ori-shalom committed Jan 20, 2023
1 parent ef2fe1b commit 5c0555e
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 30 deletions.
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,74 @@ func arrayUniqueItemsChecker(items []interface{}) bool {
}
```

## Custom function to change schema error messages

By default, the error message returned when validating a value includes the error reason, the schema, and the input value.

For example, given the following schema:

```json
{
"type": "string",
"allOf": [
{ "pattern": "[A-Z]" },
{ "pattern": "[a-z]" },
{ "pattern": "[0-9]" },
{ "pattern": "[!@#$%^&*()_+=-?~]" }
]
}
```

Passing the input value `"secret"` to this schema will produce the following error message:

```
string doesn't match the regular expression "[A-Z]"
Schema:
{
"pattern": "[A-Z]"
}
Value:
"secret"
```

Including the original value in the error message can be helpful for debugging, but it may not be appropriate for sensitive information such as secrets.

To disable the extra details in the schema error message, you can set the `openapi3.SchemaErrorDetailsDisabled` option to `true`:

```go
func main() {
// ...

// Disable schema error detailed error messages
openapi3.SchemaErrorDetailsDisabled = true

// ... other validate codes
}
```

This will shorten the error message to present only the reason:

```
string doesn't match the regular expression "[A-Z]"
```

For more fine-grained control over the error message, you can pass a custom `openapi3filter.Options` object to `openapi3filter.RequestValidationInput` that includes a `openapi3filter.CustomSchemaErrorFunc`.

```go
func validationOptions() *openapi3filter.Options {
options := openapi3filter.DefaultOptions
options.WithCustomSchemaErrorFunc(safeErrorMessage)
return options
}

func safeErrorMessage(err *openapi3.SchemaError) string {
return err.Reason
}
```

This will change the schema validation errors to return only the `Reason` field, which is guaranteed to not include the original value.

## Sub-v0 breaking API changes

### v0.113.0
Expand Down
25 changes: 11 additions & 14 deletions openapi3/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
Value: value,
Schema: schema,
SchemaField: "enum",
Reason: fmt.Sprintf("value %q is not one of the allowed values", value),
Reason: fmt.Sprintf("value is not one of the allowed values %q", schema.Enum),
customizeMessageError: settings.customizeMessageError,
}
}
Expand Down Expand Up @@ -1177,16 +1177,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val

discriminatorValString, okcheck := discriminatorVal.(string)
if !okcheck {
valStr := "null"
if discriminatorVal != nil {
valStr = fmt.Sprintf("%v", discriminatorVal)
}

return &SchemaError{
Value: discriminatorVal,
Schema: schema,
SchemaField: "discriminator",
Reason: fmt.Sprintf("value of discriminator property %q is not a string: %v", pn, valStr),
Reason: fmt.Sprintf("value of discriminator property %q is not a string", pn),
}
}

Expand All @@ -1195,7 +1190,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
Value: discriminatorVal,
Schema: schema,
SchemaField: "discriminator",
Reason: fmt.Sprintf("discriminator property %q has invalid value: %q", pn, discriminatorVal),
Reason: fmt.Sprintf("discriminator property %q has invalid value", pn),
}
}
}
Expand Down Expand Up @@ -1364,7 +1359,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
Value: value,
Schema: schema,
SchemaField: "type",
Reason: fmt.Sprintf("value \"%g\" must be an integer", value),
Reason: fmt.Sprintf("value must be an integer"),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
Expand Down Expand Up @@ -1584,7 +1579,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
Value: value,
Schema: schema,
SchemaField: "pattern",
Reason: fmt.Sprintf(`string %q doesn't match the regular expression "%s"`, value, schema.Pattern),
Reason: fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
customizeMessageError: settings.customizeMessageError,
}
if !settings.multiError {
Expand Down Expand Up @@ -1945,10 +1940,12 @@ func (schema *Schema) compilePattern() (err error) {
}

type SchemaError struct {
Value interface{}
reversePath []string
Schema *Schema
SchemaField string
Value interface{}
reversePath []string
Schema *Schema
SchemaField string
// Reason is a human-readable message describing the error.
// The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages.
Reason string
Origin error
customizeMessageError func(err *SchemaError) string
Expand Down
6 changes: 3 additions & 3 deletions openapi3/schema_oneOf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func TestVisitJSON_OneOf_MissingDiscriptorValue(t *testing.T) {
"name": "snoopy",
"$type": "snake",
})
require.ErrorContains(t, err, "discriminator property \"$type\" has invalid value: \"snake\"")
require.ErrorContains(t, err, "discriminator property \"$type\" has invalid value")
}

func TestVisitJSON_OneOf_MissingField(t *testing.T) {
Expand Down Expand Up @@ -126,14 +126,14 @@ func TestVisitJSON_OneOf_BadDescriminatorType(t *testing.T) {
"scratches": true,
"$type": 1,
})
require.ErrorContains(t, err, "value of discriminator property \"$type\" is not a string: 1")
require.ErrorContains(t, err, "value of discriminator property \"$type\" is not a string")

err = s.Components.Schemas["Animal"].Value.VisitJSON(map[string]interface{}{
"name": "snoopy",
"barks": true,
"$type": nil,
})
require.ErrorContains(t, err, "value of discriminator property \"$type\" is not a string: null")
require.ErrorContains(t, err, "value of discriminator property \"$type\" is not a string")
}

func TestVisitJSON_OneOf_Path(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion openapi3filter/issue201_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ paths:
},

"invalid required header": {
err: `response header "X-Blup" doesn't match schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
err: `response header "X-Blup" doesn't match schema: string doesn't match the regular expression "^blup$"`,
headers: map[string]string{
"X-Blip": "blip",
"x-blop": "blop",
Expand Down
2 changes: 1 addition & 1 deletion openapi3filter/issue641_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ paths:
name: "failed allof pattern",
spec: allOfSpec,
req: `/items?test=999999`,
errStr: `parameter "test" in query has an error: string "999999" doesn't match the regular expression "^[0-9]{1,4}$"`,
errStr: `parameter "test" in query has an error: string doesn't match the regular expression "^[0-9]{1,4}$"`,
},
}

Expand Down
2 changes: 1 addition & 1 deletion openapi3filter/unpack_errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func Example() {
//
// ===== Start New Error =====
// @body.status:
// Error at "/status": value "invalidStatus" is not one of the allowed values
// Error at "/status": value is not one of the allowed values ["available" "pending" "sold"]
// Schema:
// {
// "description": "pet status in the store",
Expand Down
20 changes: 10 additions & 10 deletions openapi3filter/validation_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,11 @@ func getValidationTests(t *testing.T) []*validationTest {
},
wantErrParam: "status",
wantErrParamIn: "query",
wantErrSchemaReason: "value \"available,sold\" is not one of the allowed values",
wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
wantErrSchemaPath: "/0",
wantErrSchemaValue: "available,sold",
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
Title: "value \"available,sold\" is not one of the allowed values",
Title: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
Detail: "value available,sold at /0 must be one of: available, pending, sold; " +
// TODO: do we really want to use this heuristic to guess
// that they're using the wrong serialization?
Expand All @@ -262,11 +262,11 @@ func getValidationTests(t *testing.T) []*validationTest {
},
wantErrParam: "status",
wantErrParamIn: "query",
wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
wantErrSchemaPath: "/1",
wantErrSchemaValue: "watdis",
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
Title: "value \"watdis\" is not one of the allowed values",
Title: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
Detail: "value watdis at /1 must be one of: available, pending, sold",
Source: &ValidationErrorSource{Parameter: "status"}},
},
Expand All @@ -278,11 +278,11 @@ func getValidationTests(t *testing.T) []*validationTest {
},
wantErrParam: "kind",
wantErrParamIn: "query",
wantErrSchemaReason: "value \"fish,with,commas\" is not one of the allowed values",
wantErrSchemaReason: "value is not one of the allowed values [\"dog\" \"cat\" \"turtle\" \"bird,with,commas\"]",
wantErrSchemaPath: "/1",
wantErrSchemaValue: "fish,with,commas",
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
Title: "value \"fish,with,commas\" is not one of the allowed values",
Title: "value is not one of the allowed values [\"dog\" \"cat\" \"turtle\" \"bird,with,commas\"]",
Detail: "value fish,with,commas at /1 must be one of: dog, cat, turtle, bird,with,commas",
// No 'perhaps you intended' because its the right serialization format
Source: &ValidationErrorSource{Parameter: "kind"}},
Expand All @@ -304,11 +304,11 @@ func getValidationTests(t *testing.T) []*validationTest {
},
wantErrParam: "x-environment",
wantErrParamIn: "header",
wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
wantErrSchemaReason: "value is not one of the allowed values [\"demo\" \"prod\"]",
wantErrSchemaPath: "/",
wantErrSchemaValue: "watdis",
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
Title: "value \"watdis\" is not one of the allowed values",
Title: "value is not one of the allowed values [\"demo\" \"prod\"]",
Detail: "value watdis at / must be one of: demo, prod",
Source: &ValidationErrorSource{Parameter: "x-environment"}},
},
Expand All @@ -323,11 +323,11 @@ func getValidationTests(t *testing.T) []*validationTest {
r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
},
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
wantErrSchemaValue: "watdis",
wantErrSchemaPath: "/status",
wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
Title: "value \"watdis\" is not one of the allowed values",
Title: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
Detail: "value watdis at /status must be one of: available, pending, sold",
Source: &ValidationErrorSource{Pointer: "/status"}},
},
Expand Down

0 comments on commit 5c0555e

Please sign in to comment.