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