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.

795 lines
23 KiB

  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. "encoding/json"
  17. "fmt"
  18. "sort"
  19. "strings"
  20. "github.com/go-openapi/analysis"
  21. "github.com/go-openapi/errors"
  22. "github.com/go-openapi/jsonpointer"
  23. "github.com/go-openapi/loads"
  24. "github.com/go-openapi/spec"
  25. "github.com/go-openapi/strfmt"
  26. )
  27. // Spec validates an OpenAPI 2.0 specification document.
  28. //
  29. // Returns an error flattening in a single standard error, all validation messages.
  30. //
  31. // - TODO: $ref should not have siblings
  32. // - TODO: make sure documentation reflects all checks and warnings
  33. // - TODO: check on discriminators
  34. // - TODO: explicit message on unsupported keywords (better than "forbidden property"...)
  35. // - TODO: full list of unresolved refs
  36. // - TODO: validate numeric constraints (issue#581): this should be handled like defaults and examples
  37. // - TODO: option to determine if we validate for go-swagger or in a more general context
  38. // - TODO: check on required properties to support anyOf, allOf, oneOf
  39. //
  40. // NOTE: SecurityScopes are maps: no need to check uniqueness
  41. //
  42. func Spec(doc *loads.Document, formats strfmt.Registry) error {
  43. errs, _ /*warns*/ := NewSpecValidator(doc.Schema(), formats).Validate(doc)
  44. if errs.HasErrors() {
  45. return errors.CompositeValidationError(errs.Errors...)
  46. }
  47. return nil
  48. }
  49. // SpecValidator validates a swagger 2.0 spec
  50. type SpecValidator struct {
  51. schema *spec.Schema // swagger 2.0 schema
  52. spec *loads.Document
  53. analyzer *analysis.Spec
  54. expanded *loads.Document
  55. KnownFormats strfmt.Registry
  56. Options Opts // validation options
  57. }
  58. // NewSpecValidator creates a new swagger spec validator instance
  59. func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator {
  60. return &SpecValidator{
  61. schema: schema,
  62. KnownFormats: formats,
  63. Options: defaultOpts,
  64. }
  65. }
  66. // Validate validates the swagger spec
  67. func (s *SpecValidator) Validate(data interface{}) (*Result, *Result) {
  68. var sd *loads.Document
  69. errs, warnings := new(Result), new(Result)
  70. if v, ok := data.(*loads.Document); ok {
  71. sd = v
  72. }
  73. if sd == nil {
  74. errs.AddErrors(invalidDocumentMsg())
  75. return errs, warnings // no point in continuing
  76. }
  77. s.spec = sd
  78. s.analyzer = analysis.New(sd.Spec())
  79. // Swagger schema validator
  80. schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats, SwaggerSchema(true))
  81. var obj interface{}
  82. // Raw spec unmarshalling errors
  83. if err := json.Unmarshal(sd.Raw(), &obj); err != nil {
  84. // NOTE: under normal conditions, the *load.Document has been already unmarshalled
  85. // So this one is just a paranoid check on the behavior of the spec package
  86. panic(InvalidDocumentError)
  87. }
  88. defer func() {
  89. // errs holds all errors and warnings,
  90. // warnings only warnings
  91. errs.MergeAsWarnings(warnings)
  92. warnings.AddErrors(errs.Warnings...)
  93. }()
  94. errs.Merge(schv.Validate(obj)) // error -
  95. // There may be a point in continuing to try and determine more accurate errors
  96. if !s.Options.ContinueOnErrors && errs.HasErrors() {
  97. return errs, warnings // no point in continuing
  98. }
  99. errs.Merge(s.validateReferencesValid()) // error -
  100. // There may be a point in continuing to try and determine more accurate errors
  101. if !s.Options.ContinueOnErrors && errs.HasErrors() {
  102. return errs, warnings // no point in continuing
  103. }
  104. errs.Merge(s.validateDuplicateOperationIDs())
  105. errs.Merge(s.validateDuplicatePropertyNames()) // error -
  106. errs.Merge(s.validateParameters()) // error -
  107. errs.Merge(s.validateItems()) // error -
  108. // Properties in required definition MUST validate their schema
  109. // Properties SHOULD NOT be declared as both required and readOnly (warning)
  110. errs.Merge(s.validateRequiredDefinitions()) // error and warning
  111. // There may be a point in continuing to try and determine more accurate errors
  112. if !s.Options.ContinueOnErrors && errs.HasErrors() {
  113. return errs, warnings // no point in continuing
  114. }
  115. // Values provided as default MUST validate their schema
  116. df := &defaultValidator{SpecValidator: s}
  117. errs.Merge(df.Validate())
  118. // Values provided as examples MUST validate their schema
  119. // Value provided as examples in a response without schema generate a warning
  120. // Known limitations: examples in responses for mime type not application/json are ignored (warning)
  121. ex := &exampleValidator{SpecValidator: s}
  122. errs.Merge(ex.Validate())
  123. errs.Merge(s.validateNonEmptyPathParamNames())
  124. //errs.Merge(s.validateRefNoSibling()) // warning only
  125. errs.Merge(s.validateReferenced()) // warning only
  126. return errs, warnings
  127. }
  128. func (s *SpecValidator) validateNonEmptyPathParamNames() *Result {
  129. res := new(Result)
  130. if s.spec.Spec().Paths == nil {
  131. // There is no Paths object: error
  132. res.AddErrors(noValidPathMsg())
  133. } else {
  134. if s.spec.Spec().Paths.Paths == nil {
  135. // Paths may be empty: warning
  136. res.AddWarnings(noValidPathMsg())
  137. } else {
  138. for k := range s.spec.Spec().Paths.Paths {
  139. if strings.Contains(k, "{}") {
  140. res.AddErrors(emptyPathParameterMsg(k))
  141. }
  142. }
  143. }
  144. }
  145. return res
  146. }
  147. func (s *SpecValidator) validateDuplicateOperationIDs() *Result {
  148. // OperationID, if specified, must be unique across the board
  149. var analyzer *analysis.Spec
  150. if s.expanded != nil {
  151. // $ref are valid: we can analyze operations on an expanded spec
  152. analyzer = analysis.New(s.expanded.Spec())
  153. } else {
  154. // fallback on possible incomplete picture because of previous errors
  155. analyzer = s.analyzer
  156. }
  157. res := new(Result)
  158. known := make(map[string]int)
  159. for _, v := range analyzer.OperationIDs() {
  160. if v != "" {
  161. known[v]++
  162. }
  163. }
  164. for k, v := range known {
  165. if v > 1 {
  166. res.AddErrors(nonUniqueOperationIDMsg(k, v))
  167. }
  168. }
  169. return res
  170. }
  171. type dupProp struct {
  172. Name string
  173. Definition string
  174. }
  175. func (s *SpecValidator) validateDuplicatePropertyNames() *Result {
  176. // definition can't declare a property that's already defined by one of its ancestors
  177. res := new(Result)
  178. for k, sch := range s.spec.Spec().Definitions {
  179. if len(sch.AllOf) == 0 {
  180. continue
  181. }
  182. knownanc := map[string]struct{}{
  183. "#/definitions/" + k: {},
  184. }
  185. ancs, rec := s.validateCircularAncestry(k, sch, knownanc)
  186. if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
  187. res.Merge(rec)
  188. }
  189. if len(ancs) > 0 {
  190. res.AddErrors(circularAncestryDefinitionMsg(k, ancs))
  191. return res
  192. }
  193. knowns := make(map[string]struct{})
  194. dups, rep := s.validateSchemaPropertyNames(k, sch, knowns)
  195. if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
  196. res.Merge(rep)
  197. }
  198. if len(dups) > 0 {
  199. var pns []string
  200. for _, v := range dups {
  201. pns = append(pns, v.Definition+"."+v.Name)
  202. }
  203. res.AddErrors(duplicatePropertiesMsg(k, pns))
  204. }
  205. }
  206. return res
  207. }
  208. func (s *SpecValidator) resolveRef(ref *spec.Ref) (*spec.Schema, error) {
  209. if s.spec.SpecFilePath() != "" {
  210. return spec.ResolveRefWithBase(s.spec.Spec(), ref, &spec.ExpandOptions{RelativeBase: s.spec.SpecFilePath()})
  211. }
  212. // NOTE: it looks like with the new spec resolver, this code is now unrecheable
  213. return spec.ResolveRef(s.spec.Spec(), ref)
  214. }
  215. func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) ([]dupProp, *Result) {
  216. var dups []dupProp
  217. schn := nm
  218. schc := &sch
  219. res := new(Result)
  220. for schc.Ref.String() != "" {
  221. // gather property names
  222. reso, err := s.resolveRef(&schc.Ref)
  223. if err != nil {
  224. errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
  225. return dups, res
  226. }
  227. schc = reso
  228. schn = sch.Ref.String()
  229. }
  230. if len(schc.AllOf) > 0 {
  231. for _, chld := range schc.AllOf {
  232. dup, rep := s.validateSchemaPropertyNames(schn, chld, knowns)
  233. if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
  234. res.Merge(rep)
  235. }
  236. dups = append(dups, dup...)
  237. }
  238. return dups, res
  239. }
  240. for k := range schc.Properties {
  241. _, ok := knowns[k]
  242. if ok {
  243. dups = append(dups, dupProp{Name: k, Definition: schn})
  244. } else {
  245. knowns[k] = struct{}{}
  246. }
  247. }
  248. return dups, res
  249. }
  250. func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) {
  251. res := new(Result)
  252. if sch.Ref.String() == "" && len(sch.AllOf) == 0 { // Safeguard. We should not be able to actually get there
  253. return nil, res
  254. }
  255. var ancs []string
  256. schn := nm
  257. schc := &sch
  258. for schc.Ref.String() != "" {
  259. reso, err := s.resolveRef(&schc.Ref)
  260. if err != nil {
  261. errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
  262. return ancs, res
  263. }
  264. schc = reso
  265. schn = sch.Ref.String()
  266. }
  267. if schn != nm && schn != "" {
  268. if _, ok := knowns[schn]; ok {
  269. ancs = append(ancs, schn)
  270. }
  271. knowns[schn] = struct{}{}
  272. if len(ancs) > 0 {
  273. return ancs, res
  274. }
  275. }
  276. if len(schc.AllOf) > 0 {
  277. for _, chld := range schc.AllOf {
  278. if chld.Ref.String() != "" || len(chld.AllOf) > 0 {
  279. anc, rec := s.validateCircularAncestry(schn, chld, knowns)
  280. if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
  281. res.Merge(rec)
  282. }
  283. ancs = append(ancs, anc...)
  284. if len(ancs) > 0 {
  285. return ancs, res
  286. }
  287. }
  288. }
  289. }
  290. return ancs, res
  291. }
  292. func (s *SpecValidator) validateItems() *Result {
  293. // validate parameter, items, schema and response objects for presence of item if type is array
  294. res := new(Result)
  295. for method, pi := range s.analyzer.Operations() {
  296. for path, op := range pi {
  297. for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
  298. if param.TypeName() == arrayType && param.ItemsTypeName() == "" {
  299. res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
  300. continue
  301. }
  302. if param.In != swaggerBody {
  303. if param.Items != nil {
  304. items := param.Items
  305. for items.TypeName() == arrayType {
  306. if items.ItemsTypeName() == "" {
  307. res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
  308. break
  309. }
  310. items = items.Items
  311. }
  312. }
  313. } else {
  314. // In: body
  315. if param.Schema != nil {
  316. res.Merge(s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID))
  317. }
  318. }
  319. }
  320. var responses []spec.Response
  321. if op.Responses != nil {
  322. if op.Responses.Default != nil {
  323. responses = append(responses, *op.Responses.Default)
  324. }
  325. if op.Responses.StatusCodeResponses != nil {
  326. for _, v := range op.Responses.StatusCodeResponses {
  327. responses = append(responses, v)
  328. }
  329. }
  330. }
  331. for _, resp := range responses {
  332. // Response headers with array
  333. for hn, hv := range resp.Headers {
  334. if hv.TypeName() == arrayType && hv.ItemsTypeName() == "" {
  335. res.AddErrors(arrayInHeaderRequiresItemsMsg(hn, op.ID))
  336. }
  337. }
  338. if resp.Schema != nil {
  339. res.Merge(s.validateSchemaItems(*resp.Schema, "response body", op.ID))
  340. }
  341. }
  342. }
  343. }
  344. return res
  345. }
  346. // Verifies constraints on array type
  347. func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) *Result {
  348. res := new(Result)
  349. if !schema.Type.Contains(arrayType) {
  350. return res
  351. }
  352. if schema.Items == nil || schema.Items.Len() == 0 {
  353. res.AddErrors(arrayRequiresItemsMsg(prefix, opID))
  354. return res
  355. }
  356. if schema.Items.Schema != nil {
  357. schema = *schema.Items.Schema
  358. if _, err := compileRegexp(schema.Pattern); err != nil {
  359. res.AddErrors(invalidItemsPatternMsg(prefix, opID, schema.Pattern))
  360. }
  361. res.Merge(s.validateSchemaItems(schema, prefix, opID))
  362. }
  363. return res
  364. }
  365. func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result {
  366. // Each defined operation path parameters must correspond to a named element in the API's path pattern.
  367. // (For example, you cannot have a path parameter named id for the following path /pets/{petId} but you must have a path parameter named petId.)
  368. res := new(Result)
  369. for _, l := range fromPath {
  370. var matched bool
  371. for _, r := range fromOperation {
  372. if l == "{"+r+"}" {
  373. matched = true
  374. break
  375. }
  376. }
  377. if !matched {
  378. res.AddErrors(noParameterInPathMsg(l))
  379. }
  380. }
  381. for _, p := range fromOperation {
  382. var matched bool
  383. for _, r := range fromPath {
  384. if "{"+p+"}" == r {
  385. matched = true
  386. break
  387. }
  388. }
  389. if !matched {
  390. res.AddErrors(pathParamNotInPathMsg(path, p))
  391. }
  392. }
  393. return res
  394. }
  395. func (s *SpecValidator) validateReferenced() *Result {
  396. var res Result
  397. res.MergeAsWarnings(s.validateReferencedParameters())
  398. res.MergeAsWarnings(s.validateReferencedResponses())
  399. res.MergeAsWarnings(s.validateReferencedDefinitions())
  400. return &res
  401. }
  402. // nolint: dupl
  403. func (s *SpecValidator) validateReferencedParameters() *Result {
  404. // Each referenceable definition should have references.
  405. params := s.spec.Spec().Parameters
  406. if len(params) == 0 {
  407. return nil
  408. }
  409. expected := make(map[string]struct{})
  410. for k := range params {
  411. expected["#/parameters/"+jsonpointer.Escape(k)] = struct{}{}
  412. }
  413. for _, k := range s.analyzer.AllParameterReferences() {
  414. delete(expected, k)
  415. }
  416. if len(expected) == 0 {
  417. return nil
  418. }
  419. result := new(Result)
  420. for k := range expected {
  421. result.AddWarnings(unusedParamMsg(k))
  422. }
  423. return result
  424. }
  425. // nolint: dupl
  426. func (s *SpecValidator) validateReferencedResponses() *Result {
  427. // Each referenceable definition should have references.
  428. responses := s.spec.Spec().Responses
  429. if len(responses) == 0 {
  430. return nil
  431. }
  432. expected := make(map[string]struct{})
  433. for k := range responses {
  434. expected["#/responses/"+jsonpointer.Escape(k)] = struct{}{}
  435. }
  436. for _, k := range s.analyzer.AllResponseReferences() {
  437. delete(expected, k)
  438. }
  439. if len(expected) == 0 {
  440. return nil
  441. }
  442. result := new(Result)
  443. for k := range expected {
  444. result.AddWarnings(unusedResponseMsg(k))
  445. }
  446. return result
  447. }
  448. // nolint: dupl
  449. func (s *SpecValidator) validateReferencedDefinitions() *Result {
  450. // Each referenceable definition must have references.
  451. defs := s.spec.Spec().Definitions
  452. if len(defs) == 0 {
  453. return nil
  454. }
  455. expected := make(map[string]struct{})
  456. for k := range defs {
  457. expected["#/definitions/"+jsonpointer.Escape(k)] = struct{}{}
  458. }
  459. for _, k := range s.analyzer.AllDefinitionReferences() {
  460. delete(expected, k)
  461. }
  462. if len(expected) == 0 {
  463. return nil
  464. }
  465. result := new(Result)
  466. for k := range expected {
  467. result.AddWarnings(unusedDefinitionMsg(k))
  468. }
  469. return result
  470. }
  471. func (s *SpecValidator) validateRequiredDefinitions() *Result {
  472. // Each property listed in the required array must be defined in the properties of the model
  473. res := new(Result)
  474. DEFINITIONS:
  475. for d, schema := range s.spec.Spec().Definitions {
  476. if schema.Required != nil { // Safeguard
  477. for _, pn := range schema.Required {
  478. red := s.validateRequiredProperties(pn, d, &schema)
  479. res.Merge(red)
  480. if !red.IsValid() && !s.Options.ContinueOnErrors {
  481. break DEFINITIONS // there is an error, let's stop that bleeding
  482. }
  483. }
  484. }
  485. }
  486. return res
  487. }
  488. func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Schema) *Result {
  489. // Takes care of recursive property definitions, which may be nested in additionalProperties schemas
  490. res := new(Result)
  491. propertyMatch := false
  492. patternMatch := false
  493. additionalPropertiesMatch := false
  494. isReadOnly := false
  495. // Regular properties
  496. if _, ok := v.Properties[path]; ok {
  497. propertyMatch = true
  498. isReadOnly = v.Properties[path].ReadOnly
  499. }
  500. // NOTE: patternProperties are not supported in swagger. Even though, we continue validation here
  501. // We check all defined patterns: if one regexp is invalid, croaks an error
  502. for pp, pv := range v.PatternProperties {
  503. re, err := compileRegexp(pp)
  504. if err != nil {
  505. res.AddErrors(invalidPatternMsg(pp, in))
  506. } else if re.MatchString(path) {
  507. patternMatch = true
  508. if !propertyMatch {
  509. isReadOnly = pv.ReadOnly
  510. }
  511. }
  512. }
  513. if !(propertyMatch || patternMatch) {
  514. if v.AdditionalProperties != nil {
  515. if v.AdditionalProperties.Allows && v.AdditionalProperties.Schema == nil {
  516. additionalPropertiesMatch = true
  517. } else if v.AdditionalProperties.Schema != nil {
  518. // additionalProperties as schema are upported in swagger
  519. // recursively validates additionalProperties schema
  520. // TODO : anyOf, allOf, oneOf like in schemaPropsValidator
  521. red := s.validateRequiredProperties(path, in, v.AdditionalProperties.Schema)
  522. if red.IsValid() {
  523. additionalPropertiesMatch = true
  524. if !propertyMatch && !patternMatch {
  525. isReadOnly = v.AdditionalProperties.Schema.ReadOnly
  526. }
  527. }
  528. res.Merge(red)
  529. }
  530. }
  531. }
  532. if !(propertyMatch || patternMatch || additionalPropertiesMatch) {
  533. res.AddErrors(requiredButNotDefinedMsg(path, in))
  534. }
  535. if isReadOnly {
  536. res.AddWarnings(readOnlyAndRequiredMsg(in, path))
  537. }
  538. return res
  539. }
  540. func (s *SpecValidator) validateParameters() *Result {
  541. // - for each method, path is unique, regardless of path parameters
  542. // e.g. GET:/petstore/{id}, GET:/petstore/{pet}, GET:/petstore are
  543. // considered duplicate paths
  544. // - each parameter should have a unique `name` and `type` combination
  545. // - each operation should have only 1 parameter of type body
  546. // - there must be at most 1 parameter in body
  547. // - parameters with pattern property must specify valid patterns
  548. // - $ref in parameters must resolve
  549. // - path param must be required
  550. res := new(Result)
  551. rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`)
  552. for method, pi := range s.analyzer.Operations() {
  553. methodPaths := make(map[string]map[string]string)
  554. for path, op := range pi {
  555. pathToAdd := pathHelp.stripParametersInPath(path)
  556. // Warn on garbled path afer param stripping
  557. if rexGarbledPathSegment.MatchString(pathToAdd) {
  558. res.AddWarnings(pathStrippedParamGarbledMsg(pathToAdd))
  559. }
  560. // Check uniqueness of stripped paths
  561. if _, found := methodPaths[method][pathToAdd]; found {
  562. // Sort names for stable, testable output
  563. if strings.Compare(path, methodPaths[method][pathToAdd]) < 0 {
  564. res.AddErrors(pathOverlapMsg(path, methodPaths[method][pathToAdd]))
  565. } else {
  566. res.AddErrors(pathOverlapMsg(methodPaths[method][pathToAdd], path))
  567. }
  568. } else {
  569. if _, found := methodPaths[method]; !found {
  570. methodPaths[method] = map[string]string{}
  571. }
  572. methodPaths[method][pathToAdd] = path //Original non stripped path
  573. }
  574. var bodyParams []string
  575. var paramNames []string
  576. var hasForm, hasBody bool
  577. // Check parameters names uniqueness for operation
  578. // TODO: should be done after param expansion
  579. res.Merge(s.checkUniqueParams(path, method, op))
  580. for _, pr := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
  581. // Validate pattern regexp for parameters with a Pattern property
  582. if _, err := compileRegexp(pr.Pattern); err != nil {
  583. res.AddErrors(invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern))
  584. }
  585. // There must be at most one parameter in body: list them all
  586. if pr.In == swaggerBody {
  587. bodyParams = append(bodyParams, fmt.Sprintf("%q", pr.Name))
  588. hasBody = true
  589. }
  590. if pr.In == "path" {
  591. paramNames = append(paramNames, pr.Name)
  592. // Path declared in path must have the required: true property
  593. if !pr.Required {
  594. res.AddErrors(pathParamRequiredMsg(op.ID, pr.Name))
  595. }
  596. }
  597. if pr.In == "formData" {
  598. hasForm = true
  599. }
  600. if !(pr.Type == numberType || pr.Type == integerType) &&
  601. (pr.Maximum != nil || pr.Minimum != nil || pr.MultipleOf != nil) {
  602. // A non-numeric parameter has validation keywords for numeric instances (number and integer)
  603. res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
  604. }
  605. if !(pr.Type == stringType) &&
  606. // A non-string parameter has validation keywords for strings
  607. (pr.MaxLength != nil || pr.MinLength != nil || pr.Pattern != "") {
  608. res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
  609. }
  610. if !(pr.Type == arrayType) &&
  611. // A non-array parameter has validation keywords for arrays
  612. (pr.MaxItems != nil || pr.MinItems != nil || pr.UniqueItems) {
  613. res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
  614. }
  615. }
  616. // In:formData and In:body are mutually exclusive
  617. if hasBody && hasForm {
  618. res.AddErrors(bothFormDataAndBodyMsg(op.ID))
  619. }
  620. // There must be at most one body param
  621. // Accurately report situations when more than 1 body param is declared (possibly unnamed)
  622. if len(bodyParams) > 1 {
  623. sort.Strings(bodyParams)
  624. res.AddErrors(multipleBodyParamMsg(op.ID, bodyParams))
  625. }
  626. // Check uniqueness of parameters in path
  627. paramsInPath := pathHelp.extractPathParams(path)
  628. for i, p := range paramsInPath {
  629. for j, q := range paramsInPath {
  630. if p == q && i > j {
  631. res.AddErrors(pathParamNotUniqueMsg(path, p, q))
  632. break
  633. }
  634. }
  635. }
  636. // Warns about possible malformed params in path
  637. rexGarbledParam := mustCompileRegexp(`{.*[{}\s]+.*}`)
  638. for _, p := range paramsInPath {
  639. if rexGarbledParam.MatchString(p) {
  640. res.AddWarnings(pathParamGarbledMsg(path, p))
  641. }
  642. }
  643. // Match params from path vs params from params section
  644. res.Merge(s.validatePathParamPresence(path, paramsInPath, paramNames))
  645. }
  646. }
  647. return res
  648. }
  649. func (s *SpecValidator) validateReferencesValid() *Result {
  650. // each reference must point to a valid object
  651. res := new(Result)
  652. for _, r := range s.analyzer.AllRefs() {
  653. if !r.IsValidURI(s.spec.SpecFilePath()) { // Safeguard - spec should always yield a valid URI
  654. res.AddErrors(invalidRefMsg(r.String()))
  655. }
  656. }
  657. if !res.HasErrors() {
  658. // NOTE: with default settings, loads.Document.Expanded()
  659. // stops on first error. Anyhow, the expand option to continue
  660. // on errors fails to report errors at all.
  661. exp, err := s.spec.Expanded()
  662. if err != nil {
  663. res.AddErrors(unresolvedReferencesMsg(err))
  664. }
  665. s.expanded = exp
  666. }
  667. return res
  668. }
  669. func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operation) *Result {
  670. // Check for duplicate parameters declaration in param section.
  671. // Each parameter should have a unique `name` and `type` combination
  672. // NOTE: this could be factorized in analysis (when constructing the params map)
  673. // However, there are some issues with such a factorization:
  674. // - analysis does not seem to fully expand params
  675. // - param keys may be altered by x-go-name
  676. res := new(Result)
  677. pnames := make(map[string]struct{})
  678. if op.Parameters != nil { // Safeguard
  679. for _, ppr := range op.Parameters {
  680. var ok bool
  681. pr, red := paramHelp.resolveParam(path, method, op.ID, &ppr, s)
  682. res.Merge(red)
  683. if pr != nil && pr.Name != "" { // params with empty name does no participate the check
  684. key := fmt.Sprintf("%s#%s", pr.In, pr.Name)
  685. if _, ok = pnames[key]; ok {
  686. res.AddErrors(duplicateParamNameMsg(pr.In, pr.Name, op.ID))
  687. }
  688. pnames[key] = struct{}{}
  689. }
  690. }
  691. }
  692. return res
  693. }
  694. // SetContinueOnErrors sets the ContinueOnErrors option for this validator.
  695. func (s *SpecValidator) SetContinueOnErrors(c bool) {
  696. s.Options.ContinueOnErrors = c
  697. }