Skip to content

Commit

Permalink
issue getkin#709 - validate response headers against their respective…
Browse files Browse the repository at this point in the history
… schema

Previously the code assumed response headers were only strings. Now the code can validate any type of response header
  • Loading branch information
Steve Lessard committed Dec 16, 2022
1 parent 7413c27 commit 09bb970
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 17 deletions.
2 changes: 1 addition & 1 deletion openapi3/parameter.go
Expand Up @@ -90,7 +90,7 @@ func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOpt
}

// Parameter is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameterObject
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
type Parameter struct {
ExtensionProps `json:"-" yaml:"-"`

Expand Down
2 changes: 1 addition & 1 deletion openapi3filter/issue201_test.go
Expand Up @@ -94,7 +94,7 @@ paths:
},

"invalid required header": {
err: `response header "X-Blup" doesn't match the schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
err: `response header "X-Blup" doesn't match schema: string "bluuuuuup" doesn't match the regular expression "^blup$"`,
headers: map[string]string{
"X-Blip": "blip",
"x-blop": "blop",
Expand Down
70 changes: 55 additions & 15 deletions openapi3filter/validate_response.go
Expand Up @@ -81,21 +81,9 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
for _, k := range headers {
s := response.Headers[k]
h := input.Header.Get(k)
if h == "" {
if s.Value.Required {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response header %q missing", k),
}
}
continue
}
if err := s.Value.Schema.Value.VisitJSON(h, opts...); err != nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response header %q doesn't match the schema", k),
Err: err,
}
err := validateResponseHeader(k, h, s, input, opts)
if err != nil {
return err
}
}

Expand Down Expand Up @@ -171,6 +159,58 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
return nil
}

func validateResponseHeader(
headerName string,
headerVal string,
s *openapi3.HeaderRef,
input *ResponseValidationInput,
opts []openapi3.SchemaValidationOption,
) error {
if headerVal == "" && s.Value.Required {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response header %q missing", headerName),
}
}

sm, err := s.Value.SerializationMethod()
if err != nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("unable to get header %q serialization method", headerName),
Err: err,
}
}

var decodedValue interface{}
dec := &headerParamDecoder{header: input.Header}
found := false
if decodedValue, found, err = decodeValue(dec, headerName, sm, s.Value.Schema, s.Value.Required); err != nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("unable to decode header %q value", headerName),
Err: err,
}
}

if found {
// If the value was found but not decoded it means there is no
// schema.Type defining how to interpret the header value
if decodedValue == nil {
// revert to the header's original value
decodedValue = headerVal
}
if err := s.Value.Schema.Value.VisitJSON(decodedValue, opts...); err != nil {
return &ResponseError{
Input: input,
Reason: fmt.Sprintf("response header %q doesn't match schema", headerName),
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.
Expand Down

0 comments on commit 09bb970

Please sign in to comment.