forked from getkin/kin-openapi
/
validate_response.go
128 lines (116 loc) · 2.97 KB
/
validate_response.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Package openapi3filter validates that requests and inputs request an OpenAPI 3 specification file.
package openapi3filter
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func ValidateResponse(c context.Context, input *ResponseValidationInput) error {
req := input.RequestValidationInput.Request
switch req.Method {
case "HEAD":
return nil
}
status := input.Status
if status < 100 {
return &ResponseError{
Input: input,
Reason: "illegal status code",
Err: fmt.Errorf("Status %d", status),
}
}
// These status codes will never be validated.
// TODO: The list is probably missing some.
switch status {
case http.StatusNotModified,
http.StatusPermanentRedirect,
http.StatusTemporaryRedirect,
http.StatusMovedPermanently:
return nil
}
route := input.RequestValidationInput.Route
options := input.Options
if options == nil {
options = DefaultOptions
}
// Find input for the current status
responses := route.Operation.Responses
if responses != nil && len(responses) > 0 {
responseRef := responses.Get(status) // Response
if responseRef == nil {
responseRef = responses.Default() // Default input
}
if responseRef == nil {
// By default, status that is not documented is allowed
if !options.IncludeResponseStatus {
return nil
}
// Other.
return &ResponseError{
Input: input,
Reason: "status is not supported",
}
}
response := responseRef.Value
if response == nil {
return &ResponseError{
Input: input,
Reason: "response has not been resolved",
}
}
content := response.Content
if len(content) > 0 && !options.ExcludeResponseBody {
inputMIME := input.Header.Get("Content-Type")
mediaType := parseMediaType(inputMIME)
contentType := content[mediaType]
if contentType == nil {
return &ResponseError{
Input: input,
Reason: "input header 'Content-type' has unexpected value",
}
}
schemaRef := contentType.Schema
if schemaRef != nil && isMediaTypeJSON(mediaType) {
schema := schemaRef.Value
// Read request body
body := input.Body
// Response would contain partial or empty input body
// after we begin reading.
// Ensure that this doesn't happen.
input.Body = nil
// Ensure we close the reader
defer body.Close()
// Read all
data, err := ioutil.ReadAll(body)
if err != nil {
return &ResponseError{
Input: input,
Reason: "reading the input body failed",
Err: err,
}
}
// Put the data back into the request
input.SetBodyBytes(data)
// Decode JSON
var value interface{}
if err := json.Unmarshal(data, &value); err != nil {
return &ResponseError{
Input: input,
Reason: "decoding JSON in the input body failed",
Err: err,
}
}
// Validate JSON with the schema
if err := schema.VisitJSON(value); err != nil {
return &ResponseError{
Input: input,
Err: err,
}
}
}
}
}
return nil
}