blob: 7ac8771094662a8aa94e664a159bad2d9caa22ae [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
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 swaggerBody = "body"
30const objectType = "object"
31
32// Helpers available at the package level
33var (
34 pathHelp *pathHelper
35 valueHelp *valueHelper
36 errorHelp *errorHelper
37 paramHelp *paramHelper
38 responseHelp *responseHelper
39)
40
41type errorHelper struct {
42 // A collection of unexported helpers for error construction
43}
44
45func (h *errorHelper) sErr(err errors.Error) *Result {
46 // Builds a Result from standard errors.Error
47 return &Result{Errors: []error{err}}
48}
49
50func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result {
51 // Provides more context on error messages
52 // reported by the jsoinpointer package by altering the passed Result
53 if err != nil {
54 res.AddErrors(cannotResolveRefMsg(fromPath, ref, err))
55 }
56 return res
57}
58
59type pathHelper struct {
60 // A collection of unexported helpers for path validation
61}
62
63func (h *pathHelper) stripParametersInPath(path string) string {
64 // Returns a path stripped from all path parameters, with multiple or trailing slashes removed.
65 //
66 // Stripping is performed on a slash-separated basis, e.g '/a{/b}' remains a{/b} and not /a.
67 // - Trailing "/" make a difference, e.g. /a/ !~ /a (ex: canary/bitbucket.org/swagger.json)
68 // - presence or absence of a parameter makes a difference, e.g. /a/{log} !~ /a/ (ex: canary/kubernetes/swagger.json)
69
70 // Regexp to extract parameters from path, with surrounding {}.
71 // NOTE: important non-greedy modifier
72 rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
73 strippedSegments := []string{}
74
75 for _, segment := range strings.Split(path, "/") {
76 strippedSegments = append(strippedSegments, rexParsePathParam.ReplaceAllString(segment, "X"))
77 }
78 return strings.Join(strippedSegments, "/")
79}
80
81func (h *pathHelper) extractPathParams(path string) (params []string) {
82 // Extracts all params from a path, with surrounding "{}"
83 rexParsePathParam := mustCompileRegexp(`{[^{}]+?}`)
84
85 for _, segment := range strings.Split(path, "/") {
86 for _, v := range rexParsePathParam.FindAllStringSubmatch(segment, -1) {
87 params = append(params, v...)
88 }
89 }
90 return
91}
92
93type valueHelper struct {
94 // A collection of unexported helpers for value validation
95}
96
97func (h *valueHelper) asInt64(val interface{}) int64 {
98 // Number conversion function for int64, without error checking
99 // (implements an implicit type upgrade).
100 v := reflect.ValueOf(val)
101 switch v.Kind() {
102 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
103 return v.Int()
104 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
105 return int64(v.Uint())
106 case reflect.Float32, reflect.Float64:
107 return int64(v.Float())
108 default:
109 //panic("Non numeric value in asInt64()")
110 return 0
111 }
112}
113
114func (h *valueHelper) asUint64(val interface{}) uint64 {
115 // Number conversion function for uint64, without error checking
116 // (implements an implicit type upgrade).
117 v := reflect.ValueOf(val)
118 switch v.Kind() {
119 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
120 return uint64(v.Int())
121 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
122 return v.Uint()
123 case reflect.Float32, reflect.Float64:
124 return uint64(v.Float())
125 default:
126 //panic("Non numeric value in asUint64()")
127 return 0
128 }
129}
130
131// Same for unsigned floats
132func (h *valueHelper) asFloat64(val interface{}) float64 {
133 // Number conversion function for float64, without error checking
134 // (implements an implicit type upgrade).
135 v := reflect.ValueOf(val)
136 switch v.Kind() {
137 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
138 return float64(v.Int())
139 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
140 return float64(v.Uint())
141 case reflect.Float32, reflect.Float64:
142 return v.Float()
143 default:
144 //panic("Non numeric value in asFloat64()")
145 return 0
146 }
147}
148
149type paramHelper struct {
150 // A collection of unexported helpers for parameters resolution
151}
152
153func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, res *Result, s *SpecValidator) (params []spec.Parameter) {
154 operation, ok := s.analyzer.OperationFor(method, path)
155 if ok {
156 // expand parameters first if necessary
157 resolvedParams := []spec.Parameter{}
158 for _, ppr := range operation.Parameters {
159 resolvedParam, red := h.resolveParam(path, method, operationID, &ppr, s)
160 res.Merge(red)
161 if resolvedParam != nil {
162 resolvedParams = append(resolvedParams, *resolvedParam)
163 }
164 }
165 // remove params with invalid expansion from Slice
166 operation.Parameters = resolvedParams
167
168 for _, ppr := range s.analyzer.SafeParamsFor(method, path,
169 func(p spec.Parameter, err error) bool {
170 // since params have already been expanded, there are few causes for error
171 res.AddErrors(someParametersBrokenMsg(path, method, operationID))
172 // original error from analyzer
173 res.AddErrors(err)
174 return true
175 }) {
176 params = append(params, ppr)
177 }
178 }
179 return
180}
181
182func (h *paramHelper) resolveParam(path, method, operationID string, param *spec.Parameter, s *SpecValidator) (*spec.Parameter, *Result) {
183 // Ensure parameter is expanded
184 var err error
185 res := new(Result)
186 isRef := param.Ref.String() != ""
187 if s.spec.SpecFilePath() == "" {
188 err = spec.ExpandParameterWithRoot(param, s.spec.Spec(), nil)
189 } else {
190 err = spec.ExpandParameter(param, s.spec.SpecFilePath())
191
192 }
193 if err != nil { // Safeguard
194 // NOTE: we may enter enter here when the whole parameter is an unresolved $ref
195 refPath := strings.Join([]string{"\"" + path + "\"", method}, ".")
196 errorHelp.addPointerError(res, err, param.Ref.String(), refPath)
197 return nil, res
198 }
199 res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef))
200 return param, res
201}
202
203func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result {
204 // Secure parameter structure after $ref resolution
205 res := new(Result)
206 simpleZero := spec.SimpleSchema{}
207 // Try to explain why... best guess
208 if pr.In == swaggerBody && (pr.SimpleSchema != simpleZero && pr.SimpleSchema.Type != objectType) {
209 if isRef {
210 // Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning...
211 // but we detect it because of the following error:
212 // schema took over Parameter for an unexplained reason
213 res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
214 }
215 res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
216 } else if pr.In != swaggerBody && pr.Schema != nil {
217 if isRef {
218 res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
219 }
220 res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation))
221 } else if (pr.In == swaggerBody && pr.Schema == nil) ||
222 (pr.In != swaggerBody && pr.SimpleSchema == simpleZero) { // Safeguard
223 // Other unexpected mishaps
224 res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
225 }
226 return res
227}
228
229type responseHelper struct {
230 // A collection of unexported helpers for response resolution
231}
232
233func (r *responseHelper) expandResponseRef(
234 response *spec.Response,
235 path string, s *SpecValidator) (*spec.Response, *Result) {
236 // Ensure response is expanded
237 var err error
238 res := new(Result)
239 if s.spec.SpecFilePath() == "" {
240 // there is no physical document to resolve $ref in response
241 err = spec.ExpandResponseWithRoot(response, s.spec.Spec(), nil)
242 } else {
243 err = spec.ExpandResponse(response, s.spec.SpecFilePath())
244 }
245 if err != nil { // Safeguard
246 // NOTE: we may enter here when the whole response is an unresolved $ref.
247 errorHelp.addPointerError(res, err, response.Ref.String(), path)
248 return nil, res
249 }
250 return response, res
251}
252
253func (r *responseHelper) responseMsgVariants(
254 responseType string,
255 responseCode int) (responseName, responseCodeAsStr string) {
256 // Path variants for messages
257 if responseType == "default" {
258 responseCodeAsStr = "default"
259 responseName = "default response"
260 } else {
261 responseCodeAsStr = strconv.Itoa(responseCode)
262 responseName = "response " + responseCodeAsStr
263 }
264 return
265}