podman
324 строки · 10.2 Кб
1// 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
17// TODO: define this as package validate/internal
18// This must be done while keeping CI intact with all tests and test coverage
19
20import (
21"reflect"
22"strconv"
23"strings"
24
25"github.com/go-openapi/errors"
26"github.com/go-openapi/spec"
27)
28
29const (
30swaggerBody = "body"
31swaggerExample = "example"
32swaggerExamples = "examples"
33)
34
35const (
36objectType = "object"
37arrayType = "array"
38stringType = "string"
39integerType = "integer"
40numberType = "number"
41booleanType = "boolean"
42fileType = "file"
43nullType = "null"
44)
45
46const (
47jsonProperties = "properties"
48jsonItems = "items"
49jsonType = "type"
50// jsonSchema = "schema"
51jsonDefault = "default"
52)
53
54const (
55stringFormatDate = "date"
56stringFormatDateTime = "date-time"
57stringFormatPassword = "password"
58stringFormatByte = "byte"
59// stringFormatBinary = "binary"
60stringFormatCreditCard = "creditcard"
61stringFormatDuration = "duration"
62stringFormatEmail = "email"
63stringFormatHexColor = "hexcolor"
64stringFormatHostname = "hostname"
65stringFormatIPv4 = "ipv4"
66stringFormatIPv6 = "ipv6"
67stringFormatISBN = "isbn"
68stringFormatISBN10 = "isbn10"
69stringFormatISBN13 = "isbn13"
70stringFormatMAC = "mac"
71stringFormatBSONObjectID = "bsonobjectid"
72stringFormatRGBColor = "rgbcolor"
73stringFormatSSN = "ssn"
74stringFormatURI = "uri"
75stringFormatUUID = "uuid"
76stringFormatUUID3 = "uuid3"
77stringFormatUUID4 = "uuid4"
78stringFormatUUID5 = "uuid5"
79
80integerFormatInt32 = "int32"
81integerFormatInt64 = "int64"
82integerFormatUInt32 = "uint32"
83integerFormatUInt64 = "uint64"
84
85numberFormatFloat32 = "float32"
86numberFormatFloat64 = "float64"
87numberFormatFloat = "float"
88numberFormatDouble = "double"
89)
90
91// Helpers available at the package level
92var (
93pathHelp *pathHelper
94valueHelp *valueHelper
95errorHelp *errorHelper
96paramHelp *paramHelper
97responseHelp *responseHelper
98)
99
100type errorHelper struct {
101// A collection of unexported helpers for error construction
102}
103
104func (h *errorHelper) sErr(err errors.Error) *Result {
105// Builds a Result from standard errors.Error
106return &Result{Errors: []error{err}}
107}
108
109func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result {
110// Provides more context on error messages
111// reported by the jsoinpointer package by altering the passed Result
112if err != nil {
113res.AddErrors(cannotResolveRefMsg(fromPath, ref, err))
114}
115return res
116}
117
118type pathHelper struct {
119// A collection of unexported helpers for path validation
120}
121
122func (h *pathHelper) stripParametersInPath(path string) string {
123// Returns a path stripped from all path parameters, with multiple or trailing slashes removed.
124//
125// Stripping is performed on a slash-separated basis, e.g '/a{/b}' remains a{/b} and not /a.
126// - Trailing "/" make a difference, e.g. /a/ !~ /a (ex: canary/bitbucket.org/swagger.json)
127// - presence or absence of a parameter makes a difference, e.g. /a/{log} !~ /a/ (ex: canary/kubernetes/swagger.json)
128
129// Regexp to extract parameters from path, with surrounding {}.
130// NOTE: important non-greedy modifier
131rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
132strippedSegments := []string{}
133
134for _, segment := range strings.Split(path, "/") {
135strippedSegments = append(strippedSegments, rexParsePathParam.ReplaceAllString(segment, "X"))
136}
137return strings.Join(strippedSegments, "/")
138}
139
140func (h *pathHelper) extractPathParams(path string) (params []string) {
141// Extracts all params from a path, with surrounding "{}"
142rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
143
144for _, segment := range strings.Split(path, "/") {
145for _, v := range rexParsePathParam.FindAllStringSubmatch(segment, -1) {
146params = append(params, v...)
147}
148}
149return
150}
151
152type valueHelper struct {
153// A collection of unexported helpers for value validation
154}
155
156func (h *valueHelper) asInt64(val interface{}) int64 {
157// Number conversion function for int64, without error checking
158// (implements an implicit type upgrade).
159v := reflect.ValueOf(val)
160switch v.Kind() {
161case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
162return v.Int()
163case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
164return int64(v.Uint())
165case reflect.Float32, reflect.Float64:
166return int64(v.Float())
167default:
168// panic("Non numeric value in asInt64()")
169return 0
170}
171}
172
173func (h *valueHelper) asUint64(val interface{}) uint64 {
174// Number conversion function for uint64, without error checking
175// (implements an implicit type upgrade).
176v := reflect.ValueOf(val)
177switch v.Kind() {
178case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
179return uint64(v.Int())
180case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
181return v.Uint()
182case reflect.Float32, reflect.Float64:
183return uint64(v.Float())
184default:
185// panic("Non numeric value in asUint64()")
186return 0
187}
188}
189
190// Same for unsigned floats
191func (h *valueHelper) asFloat64(val interface{}) float64 {
192// Number conversion function for float64, without error checking
193// (implements an implicit type upgrade).
194v := reflect.ValueOf(val)
195switch v.Kind() {
196case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
197return float64(v.Int())
198case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
199return float64(v.Uint())
200case reflect.Float32, reflect.Float64:
201return v.Float()
202default:
203// panic("Non numeric value in asFloat64()")
204return 0
205}
206}
207
208type paramHelper struct {
209// A collection of unexported helpers for parameters resolution
210}
211
212func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, res *Result, s *SpecValidator) (params []spec.Parameter) {
213operation, ok := s.expandedAnalyzer().OperationFor(method, path)
214if ok {
215// expand parameters first if necessary
216resolvedParams := []spec.Parameter{}
217for _, ppr := range operation.Parameters {
218resolvedParam, red := h.resolveParam(path, method, operationID, &ppr, s) //#nosec
219res.Merge(red)
220if resolvedParam != nil {
221resolvedParams = append(resolvedParams, *resolvedParam)
222}
223}
224// remove params with invalid expansion from Slice
225operation.Parameters = resolvedParams
226
227for _, ppr := range s.expandedAnalyzer().SafeParamsFor(method, path,
228func(p spec.Parameter, err error) bool {
229// since params have already been expanded, there are few causes for error
230res.AddErrors(someParametersBrokenMsg(path, method, operationID))
231// original error from analyzer
232res.AddErrors(err)
233return true
234}) {
235params = append(params, ppr)
236}
237}
238return
239}
240
241func (h *paramHelper) resolveParam(path, method, operationID string, param *spec.Parameter, s *SpecValidator) (*spec.Parameter, *Result) {
242// Ensure parameter is expanded
243var err error
244res := new(Result)
245isRef := param.Ref.String() != ""
246if s.spec.SpecFilePath() == "" {
247err = spec.ExpandParameterWithRoot(param, s.spec.Spec(), nil)
248} else {
249err = spec.ExpandParameter(param, s.spec.SpecFilePath())
250
251}
252if err != nil { // Safeguard
253// NOTE: we may enter enter here when the whole parameter is an unresolved $ref
254refPath := strings.Join([]string{"\"" + path + "\"", method}, ".")
255errorHelp.addPointerError(res, err, param.Ref.String(), refPath)
256return nil, res
257}
258res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef))
259return param, res
260}
261
262func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result {
263// Secure parameter structure after $ref resolution
264res := new(Result)
265simpleZero := spec.SimpleSchema{}
266// Try to explain why... best guess
267switch {
268case pr.In == swaggerBody && (pr.SimpleSchema != simpleZero && pr.SimpleSchema.Type != objectType):
269if isRef {
270// Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning...
271// but we detect it because of the following error:
272// schema took over Parameter for an unexplained reason
273res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
274}
275res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
276case pr.In != swaggerBody && pr.Schema != nil:
277if isRef {
278res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
279}
280res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation))
281case (pr.In == swaggerBody && pr.Schema == nil) || (pr.In != swaggerBody && pr.SimpleSchema == simpleZero):
282// Other unexpected mishaps
283res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
284}
285return res
286}
287
288type responseHelper struct {
289// A collection of unexported helpers for response resolution
290}
291
292func (r *responseHelper) expandResponseRef(
293response *spec.Response,
294path string, s *SpecValidator) (*spec.Response, *Result) {
295// Ensure response is expanded
296var err error
297res := new(Result)
298if s.spec.SpecFilePath() == "" {
299// there is no physical document to resolve $ref in response
300err = spec.ExpandResponseWithRoot(response, s.spec.Spec(), nil)
301} else {
302err = spec.ExpandResponse(response, s.spec.SpecFilePath())
303}
304if err != nil { // Safeguard
305// NOTE: we may enter here when the whole response is an unresolved $ref.
306errorHelp.addPointerError(res, err, response.Ref.String(), path)
307return nil, res
308}
309return response, res
310}
311
312func (r *responseHelper) responseMsgVariants(
313responseType string,
314responseCode int) (responseName, responseCodeAsStr string) {
315// Path variants for messages
316if responseType == jsonDefault {
317responseCodeAsStr = jsonDefault
318responseName = "default response"
319} else {
320responseCodeAsStr = strconv.Itoa(responseCode)
321responseName = "response " + responseCodeAsStr
322}
323return
324}
325