-
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
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validate
// TODO: define this as package validate/internal
// This must be done while keeping CI intact with all tests and test coverage
import (
"reflect"
"strconv"
"strings"
"github.com/go-openapi/errors"
"github.com/go-openapi/spec"
)
const (
swaggerBody = "body"
swaggerExample = "example"
swaggerExamples = "examples"
)
const (
objectType = "object"
arrayType = "array"
stringType = "string"
integerType = "integer"
numberType = "number"
booleanType = "boolean"
fileType = "file"
nullType = "null"
)
const (
jsonProperties = "properties"
jsonItems = "items"
jsonType = "type"
// jsonSchema = "schema"
jsonDefault = "default"
)
const (
stringFormatDate = "date"
stringFormatDateTime = "date-time"
stringFormatPassword = "password"
stringFormatByte = "byte"
// stringFormatBinary = "binary"
stringFormatCreditCard = "creditcard"
stringFormatDuration = "duration"
stringFormatEmail = "email"
stringFormatHexColor = "hexcolor"
stringFormatHostname = "hostname"
stringFormatIPv4 = "ipv4"
stringFormatIPv6 = "ipv6"
stringFormatISBN = "isbn"
stringFormatISBN10 = "isbn10"
stringFormatISBN13 = "isbn13"
stringFormatMAC = "mac"
stringFormatBSONObjectID = "bsonobjectid"
stringFormatRGBColor = "rgbcolor"
stringFormatSSN = "ssn"
stringFormatURI = "uri"
stringFormatUUID = "uuid"
stringFormatUUID3 = "uuid3"
stringFormatUUID4 = "uuid4"
stringFormatUUID5 = "uuid5"
integerFormatInt32 = "int32"
integerFormatInt64 = "int64"
integerFormatUInt32 = "uint32"
integerFormatUInt64 = "uint64"
numberFormatFloat32 = "float32"
numberFormatFloat64 = "float64"
numberFormatFloat = "float"
numberFormatDouble = "double"
)
// Helpers available at the package level
var (
pathHelp *pathHelper
valueHelp *valueHelper
errorHelp *errorHelper
paramHelp *paramHelper
responseHelp *responseHelper
)
type errorHelper struct {
// A collection of unexported helpers for error construction
}
func (h *errorHelper) sErr(err errors.Error) *Result {
// Builds a Result from standard errors.Error
return &Result{Errors: []error{err}}
}
func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result {
// Provides more context on error messages
// reported by the jsoinpointer package by altering the passed Result
if err != nil {
res.AddErrors(cannotResolveRefMsg(fromPath, ref, err))
}
return res
}
type pathHelper struct {
// A collection of unexported helpers for path validation
}
func (h *pathHelper) stripParametersInPath(path string) string {
// Returns a path stripped from all path parameters, with multiple or trailing slashes removed.
//
// Stripping is performed on a slash-separated basis, e.g '/a{/b}' remains a{/b} and not /a.
// - Trailing "/" make a difference, e.g. /a/ !~ /a (ex: canary/bitbucket.org/swagger.json)
// - presence or absence of a parameter makes a difference, e.g. /a/{log} !~ /a/ (ex: canary/kubernetes/swagger.json)
// Regexp to extract parameters from path, with surrounding {}.
// NOTE: important non-greedy modifier
rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
strippedSegments := []string{}
for _, segment := range strings.Split(path, "/") {
strippedSegments = append(strippedSegments, rexParsePathParam.ReplaceAllString(segment, "X"))
}
return strings.Join(strippedSegments, "/")
}
func (h *pathHelper) extractPathParams(path string) (params []string) {
// Extracts all params from a path, with surrounding "{}"
rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
for _, segment := range strings.Split(path, "/") {
for _, v := range rexParsePathParam.FindAllStringSubmatch(segment, -1) {
params = append(params, v...)
}
}
return
}
type valueHelper struct {
// A collection of unexported helpers for value validation
}
func (h *valueHelper) asInt64(val interface{}) int64 {
// Number conversion function for int64, without error checking
// (implements an implicit type upgrade).
v := reflect.ValueOf(val)
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return int64(v.Uint())
case reflect.Float32, reflect.Float64:
return int64(v.Float())
default:
// panic("Non numeric value in asInt64()")
return 0
}
}
func (h *valueHelper) asUint64(val interface{}) uint64 {
// Number conversion function for uint64, without error checking
// (implements an implicit type upgrade).
v := reflect.ValueOf(val)
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return uint64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return v.Uint()
case reflect.Float32, reflect.Float64:
return uint64(v.Float())
default:
// panic("Non numeric value in asUint64()")
return 0
}
}
// Same for unsigned floats
func (h *valueHelper) asFloat64(val interface{}) float64 {
// Number conversion function for float64, without error checking
// (implements an implicit type upgrade).
v := reflect.ValueOf(val)
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return float64(v.Uint())
case reflect.Float32, reflect.Float64:
return v.Float()
default:
// panic("Non numeric value in asFloat64()")
return 0
}
}
type paramHelper struct {
// A collection of unexported helpers for parameters resolution
}
func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, res *Result, s *SpecValidator) (params []spec.Parameter) {
operation, ok := s.analyzer.OperationFor(method, path)
if ok {
// expand parameters first if necessary
resolvedParams := []spec.Parameter{}
for _, ppr := range operation.Parameters {
resolvedParam, red := h.resolveParam(path, method, operationID, &ppr, s) //#nosec
res.Merge(red)
if resolvedParam != nil {
resolvedParams = append(resolvedParams, *resolvedParam)
}
}
// remove params with invalid expansion from Slice
operation.Parameters = resolvedParams
for _, ppr := range s.analyzer.SafeParamsFor(method, path,
func(p spec.Parameter, err error) bool {
// since params have already been expanded, there are few causes for error
res.AddErrors(someParametersBrokenMsg(path, method, operationID))
// original error from analyzer
res.AddErrors(err)
return true
}) {
params = append(params, ppr)
}
}
return
}
func (h *paramHelper) resolveParam(path, method, operationID string, param *spec.Parameter, s *SpecValidator) (*spec.Parameter, *Result) {
// Ensure parameter is expanded
var err error
res := new(Result)
isRef := param.Ref.String() != ""
if s.spec.SpecFilePath() == "" {
err = spec.ExpandParameterWithRoot(param, s.spec.Spec(), nil)
} else {
err = spec.ExpandParameter(param, s.spec.SpecFilePath())
}
if err != nil { // Safeguard
// NOTE: we may enter enter here when the whole parameter is an unresolved $ref
refPath := strings.Join([]string{"\"" + path + "\"", method}, ".")
errorHelp.addPointerError(res, err, param.Ref.String(), refPath)
return nil, res
}
res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef))
return param, res
}
func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result {
// Secure parameter structure after $ref resolution
res := new(Result)
simpleZero := spec.SimpleSchema{}
// Try to explain why... best guess
switch {
case pr.In == swaggerBody && (pr.SimpleSchema != simpleZero && pr.SimpleSchema.Type != objectType):
if isRef {
// Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning...
// but we detect it because of the following error:
// schema took over Parameter for an unexplained reason
res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
}
res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
case pr.In != swaggerBody && pr.Schema != nil:
if isRef {
res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
}
res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation))
case (pr.In == swaggerBody && pr.Schema == nil) || (pr.In != swaggerBody && pr.SimpleSchema == simpleZero):
// Other unexpected mishaps
res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
}
return res
}
type responseHelper struct {
// A collection of unexported helpers for response resolution
}
func (r *responseHelper) expandResponseRef(
response *spec.Response,
path string, s *SpecValidator) (*spec.Response, *Result) {
// Ensure response is expanded
var err error
res := new(Result)
if s.spec.SpecFilePath() == "" {
// there is no physical document to resolve $ref in response
err = spec.ExpandResponseWithRoot(response, s.spec.Spec(), nil)
} else {
err = spec.ExpandResponse(response, s.spec.SpecFilePath())
}
if err != nil { // Safeguard
// NOTE: we may enter here when the whole response is an unresolved $ref.
errorHelp.addPointerError(res, err, response.Ref.String(), path)
return nil, res
}
return response, res
}
func (r *responseHelper) responseMsgVariants(
responseType string,
responseCode int) (responseName, responseCodeAsStr string) {
// Path variants for messages
if responseType == jsonDefault {
responseCodeAsStr = jsonDefault
responseName = "default response"
} else {
responseCodeAsStr = strconv.Itoa(responseCode)
responseName = "response " + responseCodeAsStr
}
return
}