| // 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" |
| const objectType = "object" |
| |
| // 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) |
| 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 |
| if 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)) |
| } else if pr.In != swaggerBody && pr.Schema != nil { |
| if isRef { |
| res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation)) |
| } |
| res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation)) |
| } else if (pr.In == swaggerBody && pr.Schema == nil) || |
| (pr.In != swaggerBody && pr.SimpleSchema == simpleZero) { // Safeguard |
| // 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 == "default" { |
| responseCodeAsStr = "default" |
| responseName = "default response" |
| } else { |
| responseCodeAsStr = strconv.Itoa(responseCode) |
| responseName = "response " + responseCodeAsStr |
| } |
| return |
| } |