You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

helpers.go 8.7KB

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