blob: b2acf1055c2cc9a42f6f3b5eb746590f9a1253df [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package validate
16
17import (
18 "fmt"
19 "strings"
20
21 "github.com/go-openapi/spec"
22)
23
24// ExampleValidator validates example values defined in a spec
25type exampleValidator struct {
26 SpecValidator *SpecValidator
27 visitedSchemas map[string]bool
28}
29
30// resetVisited resets the internal state of visited schemas
31func (ex *exampleValidator) resetVisited() {
32 ex.visitedSchemas = map[string]bool{}
33}
34
35// beingVisited asserts a schema is being visited
36func (ex *exampleValidator) beingVisited(path string) {
37 ex.visitedSchemas[path] = true
38}
39
40// isVisited tells if a path has already been visited
41func (ex *exampleValidator) isVisited(path string) bool {
42 found := ex.visitedSchemas[path]
43 if !found {
44 // search for overlapping paths
45 frags := strings.Split(path, ".")
46 if len(frags) < 2 {
47 // shortcut exit on smaller paths
48 return found
49 }
50 last := len(frags) - 1
51 var currentFragStr, parent string
52 for i := range frags {
53 if i == 0 {
54 currentFragStr = frags[last]
55 } else {
56 currentFragStr = strings.Join([]string{frags[last-i], currentFragStr}, ".")
57 }
58 if i < last {
59 parent = strings.Join(frags[0:last-i], ".")
60 } else {
61 parent = ""
62 }
63 if strings.HasSuffix(parent, currentFragStr) {
64 found = true
65 break
66 }
67 }
68 }
69 return found
70}
71
72// Validate validates the example values declared in the swagger spec
73// Example values MUST conform to their schema.
74//
75// With Swagger 2.0, examples are supported in:
76// - schemas
77// - individual property
78// - responses
79//
80func (ex *exampleValidator) Validate() (errs *Result) {
81 errs = new(Result)
82 if ex == nil || ex.SpecValidator == nil {
83 return errs
84 }
85 ex.resetVisited()
86 errs.Merge(ex.validateExampleValueValidAgainstSchema()) // error -
87
88 return errs
89}
90
91func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result {
92 // every example value that is specified must validate against the schema for that property
93 // in: schemas, properties, object, items
94 // not in: headers, parameters without schema
95
96 res := new(Result)
97 s := ex.SpecValidator
98
99 for method, pathItem := range s.analyzer.Operations() {
100 if pathItem != nil { // Safeguard
101 for path, op := range pathItem {
102 // parameters
103 for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
104
105 // As of swagger 2.0, Examples are not supported in simple parameters
106 // However, it looks like it is supported by go-openapi
107
108 // reset explored schemas to get depth-first recursive-proof exploration
109 ex.resetVisited()
110
111 // Check simple parameters first
112 // default values provided must validate against their inline definition (no explicit schema)
113 if param.Example != nil && param.Schema == nil {
114 // check param default value is valid
115 red := NewParamValidator(&param, s.KnownFormats).Validate(param.Example)
116 if red.HasErrorsOrWarnings() {
117 res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In))
118 res.MergeAsWarnings(red)
119 }
120 }
121
122 // Recursively follows Items and Schemas
123 if param.Items != nil {
124 red := ex.validateExampleValueItemsAgainstSchema(param.Name, param.In, &param, param.Items)
125 if red.HasErrorsOrWarnings() {
126 res.AddWarnings(exampleValueItemsDoesNotValidateMsg(param.Name, param.In))
127 res.Merge(red)
128 }
129 }
130
131 if param.Schema != nil {
132 // Validate example value against schema
133 red := ex.validateExampleValueSchemaAgainstSchema(param.Name, param.In, param.Schema)
134 if red.HasErrorsOrWarnings() {
135 res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In))
136 res.Merge(red)
137 }
138 }
139 }
140
141 if op.Responses != nil {
142 if op.Responses.Default != nil {
143 // Same constraint on default Response
144 res.Merge(ex.validateExampleInResponse(op.Responses.Default, "default", path, 0, op.ID))
145 }
146 // Same constraint on regular Responses
147 if op.Responses.StatusCodeResponses != nil { // Safeguard
148 for code, r := range op.Responses.StatusCodeResponses {
149 res.Merge(ex.validateExampleInResponse(&r, "response", path, code, op.ID))
150 }
151 }
152 } else {
153 // Empty op.ID means there is no meaningful operation: no need to report a specific message
154 if op.ID != "" {
155 res.AddErrors(noValidResponseMsg(op.ID))
156 }
157 }
158 }
159 }
160 }
161 if s.spec.Spec().Definitions != nil { // Safeguard
162 // reset explored schemas to get depth-first recursive-proof exploration
163 ex.resetVisited()
164 for nm, sch := range s.spec.Spec().Definitions {
165 res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("definitions.%s", nm), "body", &sch))
166 }
167 }
168 return res
169}
170
171func (ex *exampleValidator) validateExampleInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result {
172 s := ex.SpecValidator
173
174 response, res := responseHelp.expandResponseRef(resp, path, s)
175 if !res.IsValid() { // Safeguard
176 return res
177 }
178
179 responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
180
181 if response.Headers != nil { // Safeguard
182 for nm, h := range response.Headers {
183 // reset explored schemas to get depth-first recursive-proof exploration
184 ex.resetVisited()
185
186 if h.Example != nil {
187 red := NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Example)
188 if red.HasErrorsOrWarnings() {
189 res.AddWarnings(exampleValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
190 res.MergeAsWarnings(red)
191 }
192 }
193
194 // Headers have inline definition, like params
195 if h.Items != nil {
196 red := ex.validateExampleValueItemsAgainstSchema(nm, "header", &h, h.Items)
197 if red.HasErrorsOrWarnings() {
198 res.AddWarnings(exampleValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
199 res.MergeAsWarnings(red)
200 }
201 }
202
203 if _, err := compileRegexp(h.Pattern); err != nil {
204 res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
205 }
206
207 // Headers don't have schema
208 }
209 }
210 if response.Schema != nil {
211 // reset explored schemas to get depth-first recursive-proof exploration
212 ex.resetVisited()
213
214 red := ex.validateExampleValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema)
215 if red.HasErrorsOrWarnings() {
216 // Additional message to make sure the context of the error is not lost
217 res.AddWarnings(exampleValueInDoesNotValidateMsg(operationID, responseName))
218 res.Merge(red)
219 }
220 }
221
222 if response.Examples != nil {
223 if response.Schema != nil {
224 if example, ok := response.Examples["application/json"]; ok {
225 res.MergeAsWarnings(NewSchemaValidator(response.Schema, s.spec.Spec(), path, s.KnownFormats).Validate(example))
226 } else {
227 // TODO: validate other media types too
228 res.AddWarnings(examplesMimeNotSupportedMsg(operationID, responseName))
229 }
230 } else {
231 res.AddWarnings(examplesWithoutSchemaMsg(operationID, responseName))
232 }
233 }
234 return res
235}
236
237func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
238 if schema == nil || ex.isVisited(path) {
239 // Avoids recursing if we are already done with that check
240 return nil
241 }
242 ex.beingVisited(path)
243 s := ex.SpecValidator
244 res := new(Result)
245
246 if schema.Example != nil {
247 res.MergeAsWarnings(NewSchemaValidator(schema, s.spec.Spec(), path+".example", s.KnownFormats).Validate(schema.Example))
248 }
249 if schema.Items != nil {
250 if schema.Items.Schema != nil {
251 res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".items.example", in, schema.Items.Schema))
252 }
253 // Multiple schemas in items
254 if schema.Items.Schemas != nil { // Safeguard
255 for i, sch := range schema.Items.Schemas {
256 res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].example", path, i), in, &sch))
257 }
258 }
259 }
260 if _, err := compileRegexp(schema.Pattern); err != nil {
261 res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern))
262 }
263 if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
264 // NOTE: we keep validating values, even though additionalItems is unsupported in Swagger 2.0 (and 3.0 as well)
265 res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalItems", path), in, schema.AdditionalItems.Schema))
266 }
267 for propName, prop := range schema.Properties {
268 res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop))
269 }
270 for propName, prop := range schema.PatternProperties {
271 res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop))
272 }
273 if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
274 res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalProperties", path), in, schema.AdditionalProperties.Schema))
275 }
276 if schema.AllOf != nil {
277 for i, aoSch := range schema.AllOf {
278 res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch))
279 }
280 }
281 return res
282}
283
284func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path, in string, root interface{}, items *spec.Items) *Result {
285 res := new(Result)
286 s := ex.SpecValidator
287 if items != nil {
288 if items.Example != nil {
289 res.MergeAsWarnings(newItemsValidator(path, in, items, root, s.KnownFormats).Validate(0, items.Example))
290 }
291 if items.Items != nil {
292 res.Merge(ex.validateExampleValueItemsAgainstSchema(path+"[0].example", in, root, items.Items))
293 }
294 if _, err := compileRegexp(items.Pattern); err != nil {
295 res.AddErrors(invalidPatternInMsg(path, in, items.Pattern))
296 }
297 }
298 return res
299}