本站源代码
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.

300 lines
9.9KB

  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. import (
  16. "fmt"
  17. "strings"
  18. "github.com/go-openapi/spec"
  19. )
  20. // ExampleValidator validates example values defined in a spec
  21. type exampleValidator struct {
  22. SpecValidator *SpecValidator
  23. visitedSchemas map[string]bool
  24. }
  25. // resetVisited resets the internal state of visited schemas
  26. func (ex *exampleValidator) resetVisited() {
  27. ex.visitedSchemas = map[string]bool{}
  28. }
  29. // beingVisited asserts a schema is being visited
  30. func (ex *exampleValidator) beingVisited(path string) {
  31. ex.visitedSchemas[path] = true
  32. }
  33. // isVisited tells if a path has already been visited
  34. func (ex *exampleValidator) isVisited(path string) bool {
  35. found := ex.visitedSchemas[path]
  36. if !found {
  37. // search for overlapping paths
  38. frags := strings.Split(path, ".")
  39. if len(frags) < 2 {
  40. // shortcut exit on smaller paths
  41. return found
  42. }
  43. last := len(frags) - 1
  44. var currentFragStr, parent string
  45. for i := range frags {
  46. if i == 0 {
  47. currentFragStr = frags[last]
  48. } else {
  49. currentFragStr = strings.Join([]string{frags[last-i], currentFragStr}, ".")
  50. }
  51. if i < last {
  52. parent = strings.Join(frags[0:last-i], ".")
  53. } else {
  54. parent = ""
  55. }
  56. if strings.HasSuffix(parent, currentFragStr) {
  57. found = true
  58. break
  59. }
  60. }
  61. }
  62. return found
  63. }
  64. // Validate validates the example values declared in the swagger spec
  65. // Example values MUST conform to their schema.
  66. //
  67. // With Swagger 2.0, examples are supported in:
  68. // - schemas
  69. // - individual property
  70. // - responses
  71. //
  72. func (ex *exampleValidator) Validate() (errs *Result) {
  73. errs = new(Result)
  74. if ex == nil || ex.SpecValidator == nil {
  75. return errs
  76. }
  77. ex.resetVisited()
  78. errs.Merge(ex.validateExampleValueValidAgainstSchema()) // error -
  79. return errs
  80. }
  81. func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result {
  82. // every example value that is specified must validate against the schema for that property
  83. // in: schemas, properties, object, items
  84. // not in: headers, parameters without schema
  85. res := new(Result)
  86. s := ex.SpecValidator
  87. for method, pathItem := range s.analyzer.Operations() {
  88. if pathItem != nil { // Safeguard
  89. for path, op := range pathItem {
  90. // parameters
  91. for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
  92. // As of swagger 2.0, Examples are not supported in simple parameters
  93. // However, it looks like it is supported by go-openapi
  94. // reset explored schemas to get depth-first recursive-proof exploration
  95. ex.resetVisited()
  96. // Check simple parameters first
  97. // default values provided must validate against their inline definition (no explicit schema)
  98. if param.Example != nil && param.Schema == nil {
  99. // check param default value is valid
  100. red := NewParamValidator(&param, s.KnownFormats).Validate(param.Example)
  101. if red.HasErrorsOrWarnings() {
  102. res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In))
  103. res.MergeAsWarnings(red)
  104. }
  105. }
  106. // Recursively follows Items and Schemas
  107. if param.Items != nil {
  108. red := ex.validateExampleValueItemsAgainstSchema(param.Name, param.In, &param, param.Items)
  109. if red.HasErrorsOrWarnings() {
  110. res.AddWarnings(exampleValueItemsDoesNotValidateMsg(param.Name, param.In))
  111. res.Merge(red)
  112. }
  113. }
  114. if param.Schema != nil {
  115. // Validate example value against schema
  116. red := ex.validateExampleValueSchemaAgainstSchema(param.Name, param.In, param.Schema)
  117. if red.HasErrorsOrWarnings() {
  118. res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In))
  119. res.Merge(red)
  120. }
  121. }
  122. }
  123. if op.Responses != nil {
  124. if op.Responses.Default != nil {
  125. // Same constraint on default Response
  126. res.Merge(ex.validateExampleInResponse(op.Responses.Default, "default", path, 0, op.ID))
  127. }
  128. // Same constraint on regular Responses
  129. if op.Responses.StatusCodeResponses != nil { // Safeguard
  130. for code, r := range op.Responses.StatusCodeResponses {
  131. res.Merge(ex.validateExampleInResponse(&r, "response", path, code, op.ID))
  132. }
  133. }
  134. } else {
  135. // Empty op.ID means there is no meaningful operation: no need to report a specific message
  136. if op.ID != "" {
  137. res.AddErrors(noValidResponseMsg(op.ID))
  138. }
  139. }
  140. }
  141. }
  142. }
  143. if s.spec.Spec().Definitions != nil { // Safeguard
  144. // reset explored schemas to get depth-first recursive-proof exploration
  145. ex.resetVisited()
  146. for nm, sch := range s.spec.Spec().Definitions {
  147. res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("definitions.%s", nm), "body", &sch))
  148. }
  149. }
  150. return res
  151. }
  152. func (ex *exampleValidator) validateExampleInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result {
  153. s := ex.SpecValidator
  154. response, res := responseHelp.expandResponseRef(resp, path, s)
  155. if !res.IsValid() { // Safeguard
  156. return res
  157. }
  158. responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
  159. if response.Headers != nil { // Safeguard
  160. for nm, h := range response.Headers {
  161. // reset explored schemas to get depth-first recursive-proof exploration
  162. ex.resetVisited()
  163. if h.Example != nil {
  164. red := NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Example)
  165. if red.HasErrorsOrWarnings() {
  166. res.AddWarnings(exampleValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
  167. res.MergeAsWarnings(red)
  168. }
  169. }
  170. // Headers have inline definition, like params
  171. if h.Items != nil {
  172. red := ex.validateExampleValueItemsAgainstSchema(nm, "header", &h, h.Items)
  173. if red.HasErrorsOrWarnings() {
  174. res.AddWarnings(exampleValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
  175. res.MergeAsWarnings(red)
  176. }
  177. }
  178. if _, err := compileRegexp(h.Pattern); err != nil {
  179. res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
  180. }
  181. // Headers don't have schema
  182. }
  183. }
  184. if response.Schema != nil {
  185. // reset explored schemas to get depth-first recursive-proof exploration
  186. ex.resetVisited()
  187. red := ex.validateExampleValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema)
  188. if red.HasErrorsOrWarnings() {
  189. // Additional message to make sure the context of the error is not lost
  190. res.AddWarnings(exampleValueInDoesNotValidateMsg(operationID, responseName))
  191. res.Merge(red)
  192. }
  193. }
  194. if response.Examples != nil {
  195. if response.Schema != nil {
  196. if example, ok := response.Examples["application/json"]; ok {
  197. res.MergeAsWarnings(NewSchemaValidator(response.Schema, s.spec.Spec(), path, s.KnownFormats).Validate(example))
  198. } else {
  199. // TODO: validate other media types too
  200. res.AddWarnings(examplesMimeNotSupportedMsg(operationID, responseName))
  201. }
  202. } else {
  203. res.AddWarnings(examplesWithoutSchemaMsg(operationID, responseName))
  204. }
  205. }
  206. return res
  207. }
  208. func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
  209. if schema == nil || ex.isVisited(path) {
  210. // Avoids recursing if we are already done with that check
  211. return nil
  212. }
  213. ex.beingVisited(path)
  214. s := ex.SpecValidator
  215. res := new(Result)
  216. if schema.Example != nil {
  217. res.MergeAsWarnings(NewSchemaValidator(schema, s.spec.Spec(), path+".example", s.KnownFormats).Validate(schema.Example))
  218. }
  219. if schema.Items != nil {
  220. if schema.Items.Schema != nil {
  221. res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".items.example", in, schema.Items.Schema))
  222. }
  223. // Multiple schemas in items
  224. if schema.Items.Schemas != nil { // Safeguard
  225. for i, sch := range schema.Items.Schemas {
  226. res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].example", path, i), in, &sch))
  227. }
  228. }
  229. }
  230. if _, err := compileRegexp(schema.Pattern); err != nil {
  231. res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern))
  232. }
  233. if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
  234. // NOTE: we keep validating values, even though additionalItems is unsupported in Swagger 2.0 (and 3.0 as well)
  235. res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalItems", path), in, schema.AdditionalItems.Schema))
  236. }
  237. for propName, prop := range schema.Properties {
  238. res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop))
  239. }
  240. for propName, prop := range schema.PatternProperties {
  241. res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop))
  242. }
  243. if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
  244. res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalProperties", path), in, schema.AdditionalProperties.Schema))
  245. }
  246. if schema.AllOf != nil {
  247. for i, aoSch := range schema.AllOf {
  248. res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch))
  249. }
  250. }
  251. return res
  252. }
  253. func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path, in string, root interface{}, items *spec.Items) *Result {
  254. res := new(Result)
  255. s := ex.SpecValidator
  256. if items != nil {
  257. if items.Example != nil {
  258. res.MergeAsWarnings(newItemsValidator(path, in, items, root, s.KnownFormats).Validate(0, items.Example))
  259. }
  260. if items.Items != nil {
  261. res.Merge(ex.validateExampleValueItemsAgainstSchema(path+"[0].example", in, root, items.Items))
  262. }
  263. if _, err := compileRegexp(items.Pattern); err != nil {
  264. res.AddErrors(invalidPatternInMsg(path, in, items.Pattern))
  265. }
  266. }
  267. return res
  268. }
上海开阖软件有限公司 沪ICP备12045867号-1