Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openapi3filter: Include schema ref or title in response body validation errors #699

Merged
merged 11 commits into from Dec 14, 2022
2 changes: 1 addition & 1 deletion openapi3filter/options_test.go
Expand Up @@ -78,5 +78,5 @@ paths:

fmt.Println(err.Error())

// Output: request body has an error: doesn't match the schema: field "Some field" must be an integer
// Output: request body has an error: doesn't match schema: field "Some field" must be an integer
}
4 changes: 3 additions & 1 deletion openapi3filter/validate_request.go
Expand Up @@ -273,10 +273,12 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req

// Validate JSON with the schema
if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
schemaId := getSchemaIdentifier(contentType.Schema)
schemaId = prependSpaceIfNeeded(schemaId)
return &RequestError{
Input: input,
RequestBody: requestBody,
Reason: "doesn't match the schema",
Reason: fmt.Sprintf("doesn't match schema%s", schemaId),
Err: err,
}
}
Expand Down
28 changes: 27 additions & 1 deletion openapi3filter/validate_response.go
Expand Up @@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"sort"
"strings"

"github.com/getkin/kin-openapi/openapi3"
)
Expand Down Expand Up @@ -159,11 +160,36 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error

// Validate data with the schema.
if err := contentType.Schema.Value.VisitJSON(value, append(opts, openapi3.VisitAsResponse())...); err != nil {
schemaId := getSchemaIdentifier(contentType.Schema)
schemaId = prependSpaceIfNeeded(schemaId)
return &ResponseError{
Input: input,
Reason: "response body doesn't match the schema",
Reason: fmt.Sprintf("response body doesn't match schema%s", schemaId),
Err: err,
}
}
return nil
}

// getSchemaIdentifier gets something by which a schema could be identified.
// A schema by itself doesn't have a true identity field. This function makes
// a best effort to get a value that can fill that void.
func getSchemaIdentifier(schema *openapi3.SchemaRef) string {
var id string

if schema != nil {
id = strings.TrimSpace(schema.Ref)
}
if id == "" && schema.Value != nil {
id = strings.TrimSpace(schema.Value.Title)
}

return id
}

func prependSpaceIfNeeded(value string) string {
if len(value) > 0 {
value = " " + value
}
return value
}
12 changes: 6 additions & 6 deletions openapi3filter/validation_error_test.go
Expand Up @@ -322,7 +322,7 @@ func getValidationTests(t *testing.T) []*validationTest {
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"status":"watdis"}`)),
},
wantErrReason: "doesn't match the schema",
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: "value \"watdis\" is not one of the allowed values",
wantErrSchemaValue: "watdis",
wantErrSchemaPath: "/status",
Expand All @@ -336,7 +336,7 @@ func getValidationTests(t *testing.T) []*validationTest {
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet", bytes.NewBufferString(`{"name":"Bahama"}`)),
},
wantErrReason: "doesn't match the schema",
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: `property "photoUrls" is missing`,
wantErrSchemaValue: map[string]string{"name": "Bahama"},
wantErrSchemaPath: "/photoUrls",
Expand All @@ -350,7 +350,7 @@ func getValidationTests(t *testing.T) []*validationTest {
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{}}`)),
},
wantErrReason: "doesn't match the schema",
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: `property "name" is missing`,
wantErrSchemaValue: map[string]string{},
wantErrSchemaPath: "/category/name",
Expand All @@ -364,7 +364,7 @@ func getValidationTests(t *testing.T) []*validationTest {
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"name":"Bahama","photoUrls":[],"category":{"tags": [{}]}}`)),
},
wantErrReason: "doesn't match the schema",
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: `property "name" is missing`,
wantErrSchemaValue: map[string]string{},
wantErrSchemaPath: "/category/tags/0/name",
Expand All @@ -378,7 +378,7 @@ func getValidationTests(t *testing.T) []*validationTest {
r: newPetstoreRequest(t, http.MethodPost, "/pet",
bytes.NewBufferString(`{"name":"Bahama","photoUrls":"http://cat"}`)),
},
wantErrReason: "doesn't match the schema",
wantErrReason: "doesn't match schema #/components/schemas/PetWithRequired",
wantErrSchemaReason: "field must be set to array or not be present",
wantErrSchemaPath: "/photoUrls",
wantErrSchemaValue: "string",
Expand All @@ -393,7 +393,7 @@ func getValidationTests(t *testing.T) []*validationTest {
args: validationArgs{
r: newPetstoreRequest(t, http.MethodPost, "/pet2", bytes.NewBufferString(`{"name":"Bahama"}`)),
},
wantErrReason: "doesn't match the schema",
wantErrReason: "doesn't match schema",
wantErrSchemaPath: "/",
wantErrSchemaValue: map[string]string{"name": "Bahama"},
wantErrSchemaOriginReason: `property "photoUrls" is missing`,
Expand Down
2 changes: 1 addition & 1 deletion routers/gorillamux/example_test.go
Expand Up @@ -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 schema pathref.openapi.yml#/components/schemas/TestSchema: field must be set to string or not be present
// Schema:
// {
// "type": "string"
Expand Down
2 changes: 1 addition & 1 deletion routers/legacy/validate_request_test.go
Expand Up @@ -107,6 +107,6 @@ func Example() {
fmt.Println(err)
}
// Output:
// request body has an error: doesn't match the schema: input matches more than one oneOf schemas
// request body has an error: doesn't match schema: input matches more than one oneOf schemas

}