1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package validate
16
17 import (
18 "bytes"
19 "encoding/gob"
20 "encoding/json"
21 "fmt"
22 "sort"
23 "strings"
24
25 "github.com/go-openapi/analysis"
26 "github.com/go-openapi/errors"
27 "github.com/go-openapi/jsonpointer"
28 "github.com/go-openapi/loads"
29 "github.com/go-openapi/spec"
30 "github.com/go-openapi/strfmt"
31 "github.com/go-openapi/swag"
32 )
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 func Spec(doc *loads.Document, formats strfmt.Registry) error {
49 errs, _ := NewSpecValidator(doc.Schema(), formats).Validate(doc)
50 if errs.HasErrors() {
51 return errors.CompositeValidationError(errs.Errors...)
52 }
53 return nil
54 }
55
56
57 type SpecValidator struct {
58 schema *spec.Schema
59 spec *loads.Document
60 analyzer *analysis.Spec
61 expanded *loads.Document
62 KnownFormats strfmt.Registry
63 Options Opts
64 schemaOptions *SchemaValidatorOptions
65 }
66
67
68 func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator {
69
70 schemaOptions := new(SchemaValidatorOptions)
71 for _, o := range []Option{
72 SwaggerSchema(true),
73 WithRecycleValidators(true),
74
75 } {
76 o(schemaOptions)
77 }
78
79 return &SpecValidator{
80 schema: schema,
81 KnownFormats: formats,
82 Options: defaultOpts,
83 schemaOptions: schemaOptions,
84 }
85 }
86
87
88 func (s *SpecValidator) Validate(data interface{}) (*Result, *Result) {
89 s.schemaOptions.skipSchemataResult = s.Options.SkipSchemataResult
90 var sd *loads.Document
91 errs, warnings := new(Result), new(Result)
92
93 if v, ok := data.(*loads.Document); ok {
94 sd = v
95 }
96 if sd == nil {
97 errs.AddErrors(invalidDocumentMsg())
98 return errs, warnings
99 }
100 s.spec = sd
101 s.analyzer = analysis.New(sd.Spec())
102
103
104 var obj interface{}
105 if err := json.Unmarshal(sd.Raw(), &obj); err != nil {
106
107
108 panic(InvalidDocumentError)
109 }
110
111 defer func() {
112
113
114 errs.MergeAsWarnings(warnings)
115 warnings.AddErrors(errs.Warnings...)
116 }()
117
118
119 schv := newSchemaValidator(s.schema, nil, "", s.KnownFormats, s.schemaOptions)
120 errs.Merge(schv.Validate(obj))
121
122 if !s.Options.ContinueOnErrors && errs.HasErrors() {
123 return errs, warnings
124 }
125
126 errs.Merge(s.validateReferencesValid())
127
128 if !s.Options.ContinueOnErrors && errs.HasErrors() {
129 return errs, warnings
130 }
131
132 errs.Merge(s.validateDuplicateOperationIDs())
133 errs.Merge(s.validateDuplicatePropertyNames())
134 errs.Merge(s.validateParameters())
135 errs.Merge(s.validateItems())
136
137
138
139 errs.Merge(s.validateRequiredDefinitions())
140
141
142 if !s.Options.ContinueOnErrors && errs.HasErrors() {
143 return errs, warnings
144 }
145
146
147 df := &defaultValidator{SpecValidator: s, schemaOptions: s.schemaOptions}
148 errs.Merge(df.Validate())
149
150
151
152
153 ex := &exampleValidator{SpecValidator: s, schemaOptions: s.schemaOptions}
154 errs.Merge(ex.Validate())
155
156 errs.Merge(s.validateNonEmptyPathParamNames())
157
158
159 errs.Merge(s.validateReferenced())
160
161 return errs, warnings
162 }
163
164 func (s *SpecValidator) validateNonEmptyPathParamNames() *Result {
165 res := pools.poolOfResults.BorrowResult()
166 if s.spec.Spec().Paths == nil {
167
168 res.AddErrors(noValidPathMsg())
169
170 return res
171 }
172
173 if s.spec.Spec().Paths.Paths == nil {
174
175 res.AddWarnings(noValidPathMsg())
176
177 return res
178 }
179
180 for k := range s.spec.Spec().Paths.Paths {
181 if strings.Contains(k, "{}") {
182 res.AddErrors(emptyPathParameterMsg(k))
183 }
184 }
185
186 return res
187 }
188
189 func (s *SpecValidator) validateDuplicateOperationIDs() *Result {
190
191 var analyzer *analysis.Spec
192 if s.expanded != nil {
193
194 analyzer = analysis.New(s.expanded.Spec())
195 } else {
196
197 analyzer = s.analyzer
198 }
199 res := pools.poolOfResults.BorrowResult()
200 known := make(map[string]int)
201 for _, v := range analyzer.OperationIDs() {
202 if v != "" {
203 known[v]++
204 }
205 }
206 for k, v := range known {
207 if v > 1 {
208 res.AddErrors(nonUniqueOperationIDMsg(k, v))
209 }
210 }
211 return res
212 }
213
214 type dupProp struct {
215 Name string
216 Definition string
217 }
218
219 func (s *SpecValidator) validateDuplicatePropertyNames() *Result {
220
221 res := pools.poolOfResults.BorrowResult()
222 for k, sch := range s.spec.Spec().Definitions {
223 if len(sch.AllOf) == 0 {
224 continue
225 }
226
227 knownanc := map[string]struct{}{
228 "#/definitions/" + k: {},
229 }
230
231 ancs, rec := s.validateCircularAncestry(k, sch, knownanc)
232 if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
233 res.Merge(rec)
234 }
235 if len(ancs) > 0 {
236 res.AddErrors(circularAncestryDefinitionMsg(k, ancs))
237 return res
238 }
239
240 knowns := make(map[string]struct{})
241 dups, rep := s.validateSchemaPropertyNames(k, sch, knowns)
242 if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
243 res.Merge(rep)
244 }
245 if len(dups) > 0 {
246 var pns []string
247 for _, v := range dups {
248 pns = append(pns, v.Definition+"."+v.Name)
249 }
250 res.AddErrors(duplicatePropertiesMsg(k, pns))
251 }
252
253 }
254 return res
255 }
256
257 func (s *SpecValidator) resolveRef(ref *spec.Ref) (*spec.Schema, error) {
258 if s.spec.SpecFilePath() != "" {
259 return spec.ResolveRefWithBase(s.spec.Spec(), ref, &spec.ExpandOptions{RelativeBase: s.spec.SpecFilePath()})
260 }
261
262 return spec.ResolveRef(s.spec.Spec(), ref)
263 }
264
265 func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) ([]dupProp, *Result) {
266 var dups []dupProp
267
268 schn := nm
269 schc := &sch
270 res := pools.poolOfResults.BorrowResult()
271
272 for schc.Ref.String() != "" {
273
274 reso, err := s.resolveRef(&schc.Ref)
275 if err != nil {
276 errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
277 return dups, res
278 }
279 schc = reso
280 schn = sch.Ref.String()
281 }
282
283 if len(schc.AllOf) > 0 {
284 for _, chld := range schc.AllOf {
285 dup, rep := s.validateSchemaPropertyNames(schn, chld, knowns)
286 if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
287 res.Merge(rep)
288 }
289 dups = append(dups, dup...)
290 }
291 return dups, res
292 }
293
294 for k := range schc.Properties {
295 _, ok := knowns[k]
296 if ok {
297 dups = append(dups, dupProp{Name: k, Definition: schn})
298 } else {
299 knowns[k] = struct{}{}
300 }
301 }
302
303 return dups, res
304 }
305
306 func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) {
307 res := pools.poolOfResults.BorrowResult()
308
309 if sch.Ref.String() == "" && len(sch.AllOf) == 0 {
310 return nil, res
311 }
312 var ancs []string
313
314 schn := nm
315 schc := &sch
316
317 for schc.Ref.String() != "" {
318 reso, err := s.resolveRef(&schc.Ref)
319 if err != nil {
320 errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
321 return ancs, res
322 }
323 schc = reso
324 schn = sch.Ref.String()
325 }
326
327 if schn != nm && schn != "" {
328 if _, ok := knowns[schn]; ok {
329 ancs = append(ancs, schn)
330 }
331 knowns[schn] = struct{}{}
332
333 if len(ancs) > 0 {
334 return ancs, res
335 }
336 }
337
338 if len(schc.AllOf) > 0 {
339 for _, chld := range schc.AllOf {
340 if chld.Ref.String() != "" || len(chld.AllOf) > 0 {
341 anc, rec := s.validateCircularAncestry(schn, chld, knowns)
342 if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
343 res.Merge(rec)
344 }
345 ancs = append(ancs, anc...)
346 if len(ancs) > 0 {
347 return ancs, res
348 }
349 }
350 }
351 }
352 return ancs, res
353 }
354
355 func (s *SpecValidator) validateItems() *Result {
356
357 res := pools.poolOfResults.BorrowResult()
358
359 for method, pi := range s.analyzer.Operations() {
360 for path, op := range pi {
361 for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
362
363 if param.TypeName() == arrayType && param.ItemsTypeName() == "" {
364 res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
365 continue
366 }
367 if param.In != swaggerBody {
368 if param.Items != nil {
369 items := param.Items
370 for items.TypeName() == arrayType {
371 if items.ItemsTypeName() == "" {
372 res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
373 break
374 }
375 items = items.Items
376 }
377 }
378 } else {
379
380 if param.Schema != nil {
381 res.Merge(s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID))
382 }
383 }
384 }
385
386 var responses []spec.Response
387 if op.Responses != nil {
388 if op.Responses.Default != nil {
389 responses = append(responses, *op.Responses.Default)
390 }
391 if op.Responses.StatusCodeResponses != nil {
392 for _, v := range op.Responses.StatusCodeResponses {
393 responses = append(responses, v)
394 }
395 }
396 }
397
398 for _, resp := range responses {
399
400 for hn, hv := range resp.Headers {
401 if hv.TypeName() == arrayType && hv.ItemsTypeName() == "" {
402 res.AddErrors(arrayInHeaderRequiresItemsMsg(hn, op.ID))
403 }
404 }
405 if resp.Schema != nil {
406 res.Merge(s.validateSchemaItems(*resp.Schema, "response body", op.ID))
407 }
408 }
409 }
410 }
411 return res
412 }
413
414
415 func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) *Result {
416 res := pools.poolOfResults.BorrowResult()
417 if !schema.Type.Contains(arrayType) {
418 return res
419 }
420
421 if schema.Items == nil || schema.Items.Len() == 0 {
422 res.AddErrors(arrayRequiresItemsMsg(prefix, opID))
423 return res
424 }
425
426 if schema.Items.Schema != nil {
427 schema = *schema.Items.Schema
428 if _, err := compileRegexp(schema.Pattern); err != nil {
429 res.AddErrors(invalidItemsPatternMsg(prefix, opID, schema.Pattern))
430 }
431
432 res.Merge(s.validateSchemaItems(schema, prefix, opID))
433 }
434 return res
435 }
436
437 func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result {
438
439
440 res := pools.poolOfResults.BorrowResult()
441 for _, l := range fromPath {
442 var matched bool
443 for _, r := range fromOperation {
444 if l == "{"+r+"}" {
445 matched = true
446 break
447 }
448 }
449 if !matched {
450 res.AddErrors(noParameterInPathMsg(l))
451 }
452 }
453
454 for _, p := range fromOperation {
455 var matched bool
456 for _, r := range fromPath {
457 if "{"+p+"}" == r {
458 matched = true
459 break
460 }
461 }
462 if !matched {
463 res.AddErrors(pathParamNotInPathMsg(path, p))
464 }
465 }
466
467 return res
468 }
469
470 func (s *SpecValidator) validateReferenced() *Result {
471 var res Result
472 res.MergeAsWarnings(s.validateReferencedParameters())
473 res.MergeAsWarnings(s.validateReferencedResponses())
474 res.MergeAsWarnings(s.validateReferencedDefinitions())
475 return &res
476 }
477
478 func (s *SpecValidator) validateReferencedParameters() *Result {
479
480 params := s.spec.Spec().Parameters
481 if len(params) == 0 {
482 return nil
483 }
484
485 expected := make(map[string]struct{})
486 for k := range params {
487 expected["#/parameters/"+jsonpointer.Escape(k)] = struct{}{}
488 }
489 for _, k := range s.analyzer.AllParameterReferences() {
490 delete(expected, k)
491 }
492
493 if len(expected) == 0 {
494 return nil
495 }
496 result := pools.poolOfResults.BorrowResult()
497 for k := range expected {
498 result.AddWarnings(unusedParamMsg(k))
499 }
500 return result
501 }
502
503 func (s *SpecValidator) validateReferencedResponses() *Result {
504
505 responses := s.spec.Spec().Responses
506 if len(responses) == 0 {
507 return nil
508 }
509
510 expected := make(map[string]struct{})
511 for k := range responses {
512 expected["#/responses/"+jsonpointer.Escape(k)] = struct{}{}
513 }
514 for _, k := range s.analyzer.AllResponseReferences() {
515 delete(expected, k)
516 }
517
518 if len(expected) == 0 {
519 return nil
520 }
521 result := pools.poolOfResults.BorrowResult()
522 for k := range expected {
523 result.AddWarnings(unusedResponseMsg(k))
524 }
525 return result
526 }
527
528 func (s *SpecValidator) validateReferencedDefinitions() *Result {
529
530 defs := s.spec.Spec().Definitions
531 if len(defs) == 0 {
532 return nil
533 }
534
535 expected := make(map[string]struct{})
536 for k := range defs {
537 expected["#/definitions/"+jsonpointer.Escape(k)] = struct{}{}
538 }
539 for _, k := range s.analyzer.AllDefinitionReferences() {
540 delete(expected, k)
541 }
542
543 if len(expected) == 0 {
544 return nil
545 }
546
547 result := new(Result)
548 for k := range expected {
549 result.AddWarnings(unusedDefinitionMsg(k))
550 }
551 return result
552 }
553
554 func (s *SpecValidator) validateRequiredDefinitions() *Result {
555
556 res := pools.poolOfResults.BorrowResult()
557
558 DEFINITIONS:
559 for d, schema := range s.spec.Spec().Definitions {
560 if schema.Required != nil {
561 for _, pn := range schema.Required {
562 red := s.validateRequiredProperties(pn, d, &schema)
563 res.Merge(red)
564 if !red.IsValid() && !s.Options.ContinueOnErrors {
565 break DEFINITIONS
566 }
567 }
568 }
569 }
570 return res
571 }
572
573 func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Schema) *Result {
574
575 res := pools.poolOfResults.BorrowResult()
576 propertyMatch := false
577 patternMatch := false
578 additionalPropertiesMatch := false
579 isReadOnly := false
580
581
582 if _, ok := v.Properties[path]; ok {
583 propertyMatch = true
584 isReadOnly = v.Properties[path].ReadOnly
585 }
586
587
588
589 for pp, pv := range v.PatternProperties {
590 re, err := compileRegexp(pp)
591 if err != nil {
592 res.AddErrors(invalidPatternMsg(pp, in))
593 } else if re.MatchString(path) {
594 patternMatch = true
595 if !propertyMatch {
596 isReadOnly = pv.ReadOnly
597 }
598 }
599 }
600
601 if !(propertyMatch || patternMatch) {
602 if v.AdditionalProperties != nil {
603 if v.AdditionalProperties.Allows && v.AdditionalProperties.Schema == nil {
604 additionalPropertiesMatch = true
605 } else if v.AdditionalProperties.Schema != nil {
606
607
608
609 red := s.validateRequiredProperties(path, in, v.AdditionalProperties.Schema)
610 if red.IsValid() {
611 additionalPropertiesMatch = true
612 if !propertyMatch && !patternMatch {
613 isReadOnly = v.AdditionalProperties.Schema.ReadOnly
614 }
615 }
616 res.Merge(red)
617 }
618 }
619 }
620
621 if !(propertyMatch || patternMatch || additionalPropertiesMatch) {
622 res.AddErrors(requiredButNotDefinedMsg(path, in))
623 }
624
625 if isReadOnly {
626 res.AddWarnings(readOnlyAndRequiredMsg(in, path))
627 }
628 return res
629 }
630
631 func (s *SpecValidator) validateParameters() *Result {
632
633
634
635
636
637
638
639
640
641 res := pools.poolOfResults.BorrowResult()
642 rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`)
643 for method, pi := range s.expandedAnalyzer().Operations() {
644 methodPaths := make(map[string]map[string]string)
645 for path, op := range pi {
646 if s.Options.StrictPathParamUniqueness {
647 pathToAdd := pathHelp.stripParametersInPath(path)
648
649
650 if rexGarbledPathSegment.MatchString(pathToAdd) {
651 res.AddWarnings(pathStrippedParamGarbledMsg(pathToAdd))
652 }
653
654
655 if _, found := methodPaths[method][pathToAdd]; found {
656
657
658 if strings.Compare(path, methodPaths[method][pathToAdd]) < 0 {
659 res.AddErrors(pathOverlapMsg(path, methodPaths[method][pathToAdd]))
660 } else {
661 res.AddErrors(pathOverlapMsg(methodPaths[method][pathToAdd], path))
662 }
663 } else {
664 if _, found := methodPaths[method]; !found {
665 methodPaths[method] = map[string]string{}
666 }
667 methodPaths[method][pathToAdd] = path
668
669 }
670 }
671
672 var bodyParams []string
673 var paramNames []string
674 var hasForm, hasBody bool
675
676
677
678 res.Merge(s.checkUniqueParams(path, method, op))
679
680
681 origSchema, ok := s.schema.Definitions["parameter"]
682 if !ok {
683 panic("unexpected swagger schema: missing #/definitions/parameter")
684 }
685
686 paramSchema, err := deepCloneSchema(origSchema)
687 if err != nil {
688 panic(fmt.Errorf("can't clone schema: %v", err))
689 }
690
691 for _, pr := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
692
693 schv := newSchemaValidator(¶mSchema, s.schema, fmt.Sprintf("%s.%s.parameters.%s", path, method, pr.Name), s.KnownFormats, s.schemaOptions)
694 obj := swag.ToDynamicJSON(pr)
695 res.Merge(schv.Validate(obj))
696
697
698 if _, err := compileRegexp(pr.Pattern); err != nil {
699 res.AddErrors(invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern))
700 }
701
702
703 if pr.In == swaggerBody {
704 bodyParams = append(bodyParams, fmt.Sprintf("%q", pr.Name))
705 hasBody = true
706 }
707
708 if pr.In == "path" {
709 paramNames = append(paramNames, pr.Name)
710
711 if !pr.Required {
712 res.AddErrors(pathParamRequiredMsg(op.ID, pr.Name))
713 }
714 }
715
716 if pr.In == "formData" {
717 hasForm = true
718 }
719
720 if !(pr.Type == numberType || pr.Type == integerType) &&
721 (pr.Maximum != nil || pr.Minimum != nil || pr.MultipleOf != nil) {
722
723 res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
724 }
725
726 if !(pr.Type == stringType) &&
727
728 (pr.MaxLength != nil || pr.MinLength != nil || pr.Pattern != "") {
729 res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
730 }
731
732 if !(pr.Type == arrayType) &&
733
734 (pr.MaxItems != nil || pr.MinItems != nil || pr.UniqueItems) {
735 res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
736 }
737 }
738
739
740 if hasBody && hasForm {
741 res.AddErrors(bothFormDataAndBodyMsg(op.ID))
742 }
743
744
745 if len(bodyParams) > 1 {
746 sort.Strings(bodyParams)
747 res.AddErrors(multipleBodyParamMsg(op.ID, bodyParams))
748 }
749
750
751 paramsInPath := pathHelp.extractPathParams(path)
752 for i, p := range paramsInPath {
753 for j, q := range paramsInPath {
754 if p == q && i > j {
755 res.AddErrors(pathParamNotUniqueMsg(path, p, q))
756 break
757 }
758 }
759 }
760
761
762 rexGarbledParam := mustCompileRegexp(`{.*[{}\s]+.*}`)
763 for _, p := range paramsInPath {
764 if rexGarbledParam.MatchString(p) {
765 res.AddWarnings(pathParamGarbledMsg(path, p))
766 }
767 }
768
769
770 res.Merge(s.validatePathParamPresence(path, paramsInPath, paramNames))
771 }
772 }
773 return res
774 }
775
776 func (s *SpecValidator) validateReferencesValid() *Result {
777
778 res := pools.poolOfResults.BorrowResult()
779 for _, r := range s.analyzer.AllRefs() {
780 if !r.IsValidURI(s.spec.SpecFilePath()) {
781 res.AddErrors(invalidRefMsg(r.String()))
782 }
783 }
784 if !res.HasErrors() {
785
786
787
788 exp, err := s.spec.Expanded()
789 if err != nil {
790 res.AddErrors(unresolvedReferencesMsg(err))
791 }
792 s.expanded = exp
793 }
794 return res
795 }
796
797 func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operation) *Result {
798
799
800
801
802
803
804 res := pools.poolOfResults.BorrowResult()
805 pnames := make(map[string]struct{})
806
807 if op.Parameters != nil {
808 for _, ppr := range op.Parameters {
809 var ok bool
810 pr, red := paramHelp.resolveParam(path, method, op.ID, &ppr, s)
811 res.Merge(red)
812
813 if pr != nil && pr.Name != "" {
814 key := fmt.Sprintf("%s#%s", pr.In, pr.Name)
815
816 if _, ok = pnames[key]; ok {
817 res.AddErrors(duplicateParamNameMsg(pr.In, pr.Name, op.ID))
818 }
819 pnames[key] = struct{}{}
820 }
821 }
822 }
823 return res
824 }
825
826
827 func (s *SpecValidator) SetContinueOnErrors(c bool) {
828 s.Options.ContinueOnErrors = c
829 }
830
831
832
833 func (s *SpecValidator) expandedAnalyzer() *analysis.Spec {
834 if s.expanded != nil && s.expanded.Analyzer != nil {
835 return s.expanded.Analyzer
836 }
837 return s.analyzer
838 }
839
840 func deepCloneSchema(src spec.Schema) (spec.Schema, error) {
841 var b bytes.Buffer
842 if err := gob.NewEncoder(&b).Encode(src); err != nil {
843 return spec.Schema{}, err
844 }
845
846 var dst spec.Schema
847 if err := gob.NewDecoder(&b).Decode(&dst); err != nil {
848 return spec.Schema{}, err
849 }
850
851 return dst, nil
852 }
853
View as plain text