本站源代码
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

778 Zeilen
22KB

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