spec.go 23 KB

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