Serge Bazanski | cc25bdf | 2018-10-25 14:02:58 +0200 | [diff] [blame] | 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 | |
| 15 | package 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 | |
| 20 | import ( |
| 21 | "reflect" |
| 22 | "strconv" |
| 23 | "strings" |
| 24 | |
| 25 | "github.com/go-openapi/errors" |
| 26 | "github.com/go-openapi/spec" |
| 27 | ) |
| 28 | |
| 29 | const swaggerBody = "body" |
| 30 | const objectType = "object" |
| 31 | |
| 32 | // Helpers available at the package level |
| 33 | var ( |
| 34 | pathHelp *pathHelper |
| 35 | valueHelp *valueHelper |
| 36 | errorHelp *errorHelper |
| 37 | paramHelp *paramHelper |
| 38 | responseHelp *responseHelper |
| 39 | ) |
| 40 | |
| 41 | type errorHelper struct { |
| 42 | // A collection of unexported helpers for error construction |
| 43 | } |
| 44 | |
| 45 | func (h *errorHelper) sErr(err errors.Error) *Result { |
| 46 | // Builds a Result from standard errors.Error |
| 47 | return &Result{Errors: []error{err}} |
| 48 | } |
| 49 | |
| 50 | func (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 | |
| 59 | type pathHelper struct { |
| 60 | // A collection of unexported helpers for path validation |
| 61 | } |
| 62 | |
| 63 | func (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 | |
| 81 | func (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 | |
| 93 | type valueHelper struct { |
| 94 | // A collection of unexported helpers for value validation |
| 95 | } |
| 96 | |
| 97 | func (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 | |
| 114 | func (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 |
| 132 | func (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 | |
| 149 | type paramHelper struct { |
| 150 | // A collection of unexported helpers for parameters resolution |
| 151 | } |
| 152 | |
| 153 | func (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 | |
| 182 | func (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 | |
| 203 | func (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 | |
| 229 | type responseHelper struct { |
| 230 | // A collection of unexported helpers for response resolution |
| 231 | } |
| 232 | |
| 233 | func (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 | |
| 253 | func (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 | } |