Skip to content

Commit 5c0555e

Browse files
authored
openapi3: remove value data from SchemaError.Reason field (#737)
Resolves #735
1 parent ef2fe1b commit 5c0555e

File tree

7 files changed

+95
-30
lines changed

7 files changed

+95
-30
lines changed

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,74 @@ func arrayUniqueItemsChecker(items []interface{}) bool {
199199
}
200200
```
201201

202+
## Custom function to change schema error messages
203+
204+
By default, the error message returned when validating a value includes the error reason, the schema, and the input value.
205+
206+
For example, given the following schema:
207+
208+
```json
209+
{
210+
"type": "string",
211+
"allOf": [
212+
{ "pattern": "[A-Z]" },
213+
{ "pattern": "[a-z]" },
214+
{ "pattern": "[0-9]" },
215+
{ "pattern": "[!@#$%^&*()_+=-?~]" }
216+
]
217+
}
218+
```
219+
220+
Passing the input value `"secret"` to this schema will produce the following error message:
221+
222+
```
223+
string doesn't match the regular expression "[A-Z]"
224+
Schema:
225+
{
226+
"pattern": "[A-Z]"
227+
}
228+
229+
Value:
230+
"secret"
231+
```
232+
233+
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.
234+
235+
To disable the extra details in the schema error message, you can set the `openapi3.SchemaErrorDetailsDisabled` option to `true`:
236+
237+
```go
238+
func main() {
239+
// ...
240+
241+
// Disable schema error detailed error messages
242+
openapi3.SchemaErrorDetailsDisabled = true
243+
244+
// ... other validate codes
245+
}
246+
```
247+
248+
This will shorten the error message to present only the reason:
249+
250+
```
251+
string doesn't match the regular expression "[A-Z]"
252+
```
253+
254+
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`.
255+
256+
```go
257+
func validationOptions() *openapi3filter.Options {
258+
options := openapi3filter.DefaultOptions
259+
options.WithCustomSchemaErrorFunc(safeErrorMessage)
260+
return options
261+
}
262+
263+
func safeErrorMessage(err *openapi3.SchemaError) string {
264+
return err.Reason
265+
}
266+
```
267+
268+
This will change the schema validation errors to return only the `Reason` field, which is guaranteed to not include the original value.
269+
202270
## Sub-v0 breaking API changes
203271

204272
### v0.113.0

openapi3/schema.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
11381138
Value: value,
11391139
Schema: schema,
11401140
SchemaField: "enum",
1141-
Reason: fmt.Sprintf("value %q is not one of the allowed values", value),
1141+
Reason: fmt.Sprintf("value is not one of the allowed values %q", schema.Enum),
11421142
customizeMessageError: settings.customizeMessageError,
11431143
}
11441144
}
@@ -1177,16 +1177,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
11771177

11781178
discriminatorValString, okcheck := discriminatorVal.(string)
11791179
if !okcheck {
1180-
valStr := "null"
1181-
if discriminatorVal != nil {
1182-
valStr = fmt.Sprintf("%v", discriminatorVal)
1183-
}
1184-
11851180
return &SchemaError{
11861181
Value: discriminatorVal,
11871182
Schema: schema,
11881183
SchemaField: "discriminator",
1189-
Reason: fmt.Sprintf("value of discriminator property %q is not a string: %v", pn, valStr),
1184+
Reason: fmt.Sprintf("value of discriminator property %q is not a string", pn),
11901185
}
11911186
}
11921187

@@ -1195,7 +1190,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
11951190
Value: discriminatorVal,
11961191
Schema: schema,
11971192
SchemaField: "discriminator",
1198-
Reason: fmt.Sprintf("discriminator property %q has invalid value: %q", pn, discriminatorVal),
1193+
Reason: fmt.Sprintf("discriminator property %q has invalid value", pn),
11991194
}
12001195
}
12011196
}
@@ -1364,7 +1359,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
13641359
Value: value,
13651360
Schema: schema,
13661361
SchemaField: "type",
1367-
Reason: fmt.Sprintf("value \"%g\" must be an integer", value),
1362+
Reason: fmt.Sprintf("value must be an integer"),
13681363
customizeMessageError: settings.customizeMessageError,
13691364
}
13701365
if !settings.multiError {
@@ -1584,7 +1579,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
15841579
Value: value,
15851580
Schema: schema,
15861581
SchemaField: "pattern",
1587-
Reason: fmt.Sprintf(`string %q doesn't match the regular expression "%s"`, value, schema.Pattern),
1582+
Reason: fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
15881583
customizeMessageError: settings.customizeMessageError,
15891584
}
15901585
if !settings.multiError {
@@ -1945,10 +1940,12 @@ func (schema *Schema) compilePattern() (err error) {
19451940
}
19461941

19471942
type SchemaError struct {
1948-
Value interface{}
1949-
reversePath []string
1950-
Schema *Schema
1951-
SchemaField string
1943+
Value interface{}
1944+
reversePath []string
1945+
Schema *Schema
1946+
SchemaField string
1947+
// Reason is a human-readable message describing the error.
1948+
// The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages.
19521949
Reason string
19531950
Origin error
19541951
customizeMessageError func(err *SchemaError) string

openapi3/schema_oneOf_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func TestVisitJSON_OneOf_MissingDiscriptorValue(t *testing.T) {
9696
"name": "snoopy",
9797
"$type": "snake",
9898
})
99-
require.ErrorContains(t, err, "discriminator property \"$type\" has invalid value: \"snake\"")
99+
require.ErrorContains(t, err, "discriminator property \"$type\" has invalid value")
100100
}
101101

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

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

139139
func TestVisitJSON_OneOf_Path(t *testing.T) {

openapi3filter/issue201_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ paths:
9898
},
9999

100100
"invalid required header": {
101-
err: `response header "X-Blup" doesn't match schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
101+
err: `response header "X-Blup" doesn't match schema: string doesn't match the regular expression "^blup$"`,
102102
headers: map[string]string{
103103
"X-Blip": "blip",
104104
"x-blop": "blop",

openapi3filter/issue641_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ paths:
6969
name: "failed allof pattern",
7070
spec: allOfSpec,
7171
req: `/items?test=999999`,
72-
errStr: `parameter "test" in query has an error: string "999999" doesn't match the regular expression "^[0-9]{1,4}$"`,
72+
errStr: `parameter "test" in query has an error: string doesn't match the regular expression "^[0-9]{1,4}$"`,
7373
},
7474
}
7575

openapi3filter/unpack_errors_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func Example() {
9393
//
9494
// ===== Start New Error =====
9595
// @body.status:
96-
// Error at "/status": value "invalidStatus" is not one of the allowed values
96+
// Error at "/status": value is not one of the allowed values ["available" "pending" "sold"]
9797
// Schema:
9898
// {
9999
// "description": "pet status in the store",

openapi3filter/validation_error_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,11 @@ func getValidationTests(t *testing.T) []*validationTest {
244244
},
245245
wantErrParam: "status",
246246
wantErrParamIn: "query",
247-
wantErrSchemaReason: "value \"available,sold\" is not one of the allowed values",
247+
wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
248248
wantErrSchemaPath: "/0",
249249
wantErrSchemaValue: "available,sold",
250250
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
251-
Title: "value \"available,sold\" is not one of the allowed values",
251+
Title: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
252252
Detail: "value available,sold at /0 must be one of: available, pending, sold; " +
253253
// TODO: do we really want to use this heuristic to guess
254254
// that they're using the wrong serialization?
@@ -262,11 +262,11 @@ func getValidationTests(t *testing.T) []*validationTest {
262262
},
263263
wantErrParam: "status",
264264
wantErrParamIn: "query",
265-
wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
265+
wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
266266
wantErrSchemaPath: "/1",
267267
wantErrSchemaValue: "watdis",
268268
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
269-
Title: "value \"watdis\" is not one of the allowed values",
269+
Title: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
270270
Detail: "value watdis at /1 must be one of: available, pending, sold",
271271
Source: &ValidationErrorSource{Parameter: "status"}},
272272
},
@@ -278,11 +278,11 @@ func getValidationTests(t *testing.T) []*validationTest {
278278
},
279279
wantErrParam: "kind",
280280
wantErrParamIn: "query",
281-
wantErrSchemaReason: "value \"fish,with,commas\" is not one of the allowed values",
281+
wantErrSchemaReason: "value is not one of the allowed values [\"dog\" \"cat\" \"turtle\" \"bird,with,commas\"]",
282282
wantErrSchemaPath: "/1",
283283
wantErrSchemaValue: "fish,with,commas",
284284
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
285-
Title: "value \"fish,with,commas\" is not one of the allowed values",
285+
Title: "value is not one of the allowed values [\"dog\" \"cat\" \"turtle\" \"bird,with,commas\"]",
286286
Detail: "value fish,with,commas at /1 must be one of: dog, cat, turtle, bird,with,commas",
287287
// No 'perhaps you intended' because its the right serialization format
288288
Source: &ValidationErrorSource{Parameter: "kind"}},
@@ -304,11 +304,11 @@ func getValidationTests(t *testing.T) []*validationTest {
304304
},
305305
wantErrParam: "x-environment",
306306
wantErrParamIn: "header",
307-
wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
307+
wantErrSchemaReason: "value is not one of the allowed values [\"demo\" \"prod\"]",
308308
wantErrSchemaPath: "/",
309309
wantErrSchemaValue: "watdis",
310310
wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
311-
Title: "value \"watdis\" is not one of the allowed values",
311+
Title: "value is not one of the allowed values [\"demo\" \"prod\"]",
312312
Detail: "value watdis at / must be one of: demo, prod",
313313
Source: &ValidationErrorSource{Parameter: "x-environment"}},
314314
},
@@ -323,11 +323,11 @@ func getValidationTests(t *testing.T) []*validationTest {
323323
r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
324324
},
325325
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
326-
wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
326+
wantErrSchemaReason: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
327327
wantErrSchemaValue: "watdis",
328328
wantErrSchemaPath: "/status",
329329
wantErrResponse: &ValidationError{Status: http.StatusUnprocessableEntity,
330-
Title: "value \"watdis\" is not one of the allowed values",
330+
Title: "value is not one of the allowed values [\"available\" \"pending\" \"sold\"]",
331331
Detail: "value watdis at /status must be one of: available, pending, sold",
332332
Source: &ValidationErrorSource{Pointer: "/status"}},
333333
},

0 commit comments

Comments
 (0)