podman

Форк
0
804 строки · 23.2 Кб
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

15
package validate
16

17
import (
18
	"encoding/json"
19
	"fmt"
20
	"sort"
21
	"strings"
22

23
	"github.com/go-openapi/analysis"
24
	"github.com/go-openapi/errors"
25
	"github.com/go-openapi/jsonpointer"
26
	"github.com/go-openapi/loads"
27
	"github.com/go-openapi/spec"
28
	"github.com/go-openapi/strfmt"
29
)
30

31
// Spec validates an OpenAPI 2.0 specification document.
32
//
33
// Returns an error flattening in a single standard error, all validation messages.
34
//
35
//  - TODO: $ref should not have siblings
36
//  - TODO: make sure documentation reflects all checks and warnings
37
//  - TODO: check on discriminators
38
//  - TODO: explicit message on unsupported keywords (better than "forbidden property"...)
39
//  - TODO: full list of unresolved refs
40
//  - TODO: validate numeric constraints (issue#581): this should be handled like defaults and examples
41
//  - TODO: option to determine if we validate for go-swagger or in a more general context
42
//  - TODO: check on required properties to support anyOf, allOf, oneOf
43
//
44
// NOTE: SecurityScopes are maps: no need to check uniqueness
45
//
46
func Spec(doc *loads.Document, formats strfmt.Registry) error {
47
	errs, _ /*warns*/ := NewSpecValidator(doc.Schema(), formats).Validate(doc)
48
	if errs.HasErrors() {
49
		return errors.CompositeValidationError(errs.Errors...)
50
	}
51
	return nil
52
}
53

54
// SpecValidator validates a swagger 2.0 spec
55
type SpecValidator struct {
56
	schema       *spec.Schema // swagger 2.0 schema
57
	spec         *loads.Document
58
	analyzer     *analysis.Spec
59
	expanded     *loads.Document
60
	KnownFormats strfmt.Registry
61
	Options      Opts // validation options
62
}
63

64
// NewSpecValidator creates a new swagger spec validator instance
65
func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator {
66
	return &SpecValidator{
67
		schema:       schema,
68
		KnownFormats: formats,
69
		Options:      defaultOpts,
70
	}
71
}
72

73
// Validate validates the swagger spec
74
func (s *SpecValidator) Validate(data interface{}) (*Result, *Result) {
75
	var sd *loads.Document
76
	errs, warnings := new(Result), new(Result)
77

78
	if v, ok := data.(*loads.Document); ok {
79
		sd = v
80
	}
81
	if sd == nil {
82
		errs.AddErrors(invalidDocumentMsg())
83
		return errs, warnings // no point in continuing
84
	}
85
	s.spec = sd
86
	s.analyzer = analysis.New(sd.Spec())
87

88
	// Swagger schema validator
89
	schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats, SwaggerSchema(true))
90
	var obj interface{}
91

92
	// Raw spec unmarshalling errors
93
	if err := json.Unmarshal(sd.Raw(), &obj); err != nil {
94
		// NOTE: under normal conditions, the *load.Document has been already unmarshalled
95
		// So this one is just a paranoid check on the behavior of the spec package
96
		panic(InvalidDocumentError)
97
	}
98

99
	defer func() {
100
		// errs holds all errors and warnings,
101
		// warnings only warnings
102
		errs.MergeAsWarnings(warnings)
103
		warnings.AddErrors(errs.Warnings...)
104
	}()
105

106
	errs.Merge(schv.Validate(obj)) // error -
107
	// There may be a point in continuing to try and determine more accurate errors
108
	if !s.Options.ContinueOnErrors && errs.HasErrors() {
109
		return errs, warnings // no point in continuing
110
	}
111

112
	errs.Merge(s.validateReferencesValid()) // error -
113
	// There may be a point in continuing to try and determine more accurate errors
114
	if !s.Options.ContinueOnErrors && errs.HasErrors() {
115
		return errs, warnings // no point in continuing
116
	}
117

118
	errs.Merge(s.validateDuplicateOperationIDs())
119
	errs.Merge(s.validateDuplicatePropertyNames()) // error -
120
	errs.Merge(s.validateParameters())             // error -
121
	errs.Merge(s.validateItems())                  // error -
122

123
	// Properties in required definition MUST validate their schema
124
	// Properties SHOULD NOT be declared as both required and readOnly (warning)
125
	errs.Merge(s.validateRequiredDefinitions()) // error and warning
126

127
	// There may be a point in continuing to try and determine more accurate errors
128
	if !s.Options.ContinueOnErrors && errs.HasErrors() {
129
		return errs, warnings // no point in continuing
130
	}
131

132
	// Values provided as default MUST validate their schema
133
	df := &defaultValidator{SpecValidator: s}
134
	errs.Merge(df.Validate())
135

136
	// Values provided as examples MUST validate their schema
137
	// Value provided as examples in a response without schema generate a warning
138
	// Known limitations: examples in responses for mime type not application/json are ignored (warning)
139
	ex := &exampleValidator{SpecValidator: s}
140
	errs.Merge(ex.Validate())
141

142
	errs.Merge(s.validateNonEmptyPathParamNames())
143

144
	// errs.Merge(s.validateRefNoSibling()) // warning only
145
	errs.Merge(s.validateReferenced()) // warning only
146

147
	return errs, warnings
148
}
149

150
func (s *SpecValidator) validateNonEmptyPathParamNames() *Result {
151
	res := new(Result)
152
	if s.spec.Spec().Paths == nil {
153
		// There is no Paths object: error
154
		res.AddErrors(noValidPathMsg())
155
	} else {
156
		if s.spec.Spec().Paths.Paths == nil {
157
			// Paths may be empty: warning
158
			res.AddWarnings(noValidPathMsg())
159
		} else {
160
			for k := range s.spec.Spec().Paths.Paths {
161
				if strings.Contains(k, "{}") {
162
					res.AddErrors(emptyPathParameterMsg(k))
163
				}
164
			}
165
		}
166
	}
167
	return res
168
}
169

170
func (s *SpecValidator) validateDuplicateOperationIDs() *Result {
171
	// OperationID, if specified, must be unique across the board
172
	var analyzer *analysis.Spec
173
	if s.expanded != nil {
174
		// $ref are valid: we can analyze operations on an expanded spec
175
		analyzer = analysis.New(s.expanded.Spec())
176
	} else {
177
		// fallback on possible incomplete picture because of previous errors
178
		analyzer = s.analyzer
179
	}
180
	res := new(Result)
181
	known := make(map[string]int)
182
	for _, v := range analyzer.OperationIDs() {
183
		if v != "" {
184
			known[v]++
185
		}
186
	}
187
	for k, v := range known {
188
		if v > 1 {
189
			res.AddErrors(nonUniqueOperationIDMsg(k, v))
190
		}
191
	}
192
	return res
193
}
194

195
type dupProp struct {
196
	Name       string
197
	Definition string
198
}
199

200
func (s *SpecValidator) validateDuplicatePropertyNames() *Result {
201
	// definition can't declare a property that's already defined by one of its ancestors
202
	res := new(Result)
203
	for k, sch := range s.spec.Spec().Definitions {
204
		if len(sch.AllOf) == 0 {
205
			continue
206
		}
207

208
		knownanc := map[string]struct{}{
209
			"#/definitions/" + k: {},
210
		}
211

212
		ancs, rec := s.validateCircularAncestry(k, sch, knownanc)
213
		if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
214
			res.Merge(rec)
215
		}
216
		if len(ancs) > 0 {
217
			res.AddErrors(circularAncestryDefinitionMsg(k, ancs))
218
			return res
219
		}
220

221
		knowns := make(map[string]struct{})
222
		dups, rep := s.validateSchemaPropertyNames(k, sch, knowns)
223
		if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
224
			res.Merge(rep)
225
		}
226
		if len(dups) > 0 {
227
			var pns []string
228
			for _, v := range dups {
229
				pns = append(pns, v.Definition+"."+v.Name)
230
			}
231
			res.AddErrors(duplicatePropertiesMsg(k, pns))
232
		}
233

234
	}
235
	return res
236
}
237

238
func (s *SpecValidator) resolveRef(ref *spec.Ref) (*spec.Schema, error) {
239
	if s.spec.SpecFilePath() != "" {
240
		return spec.ResolveRefWithBase(s.spec.Spec(), ref, &spec.ExpandOptions{RelativeBase: s.spec.SpecFilePath()})
241
	}
242
	// NOTE: it looks like with the new spec resolver, this code is now unrecheable
243
	return spec.ResolveRef(s.spec.Spec(), ref)
244
}
245

246
func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) ([]dupProp, *Result) {
247
	var dups []dupProp
248

249
	schn := nm
250
	schc := &sch
251
	res := new(Result)
252

253
	for schc.Ref.String() != "" {
254
		// gather property names
255
		reso, err := s.resolveRef(&schc.Ref)
256
		if err != nil {
257
			errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
258
			return dups, res
259
		}
260
		schc = reso
261
		schn = sch.Ref.String()
262
	}
263

264
	if len(schc.AllOf) > 0 {
265
		for _, chld := range schc.AllOf {
266
			dup, rep := s.validateSchemaPropertyNames(schn, chld, knowns)
267
			if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
268
				res.Merge(rep)
269
			}
270
			dups = append(dups, dup...)
271
		}
272
		return dups, res
273
	}
274

275
	for k := range schc.Properties {
276
		_, ok := knowns[k]
277
		if ok {
278
			dups = append(dups, dupProp{Name: k, Definition: schn})
279
		} else {
280
			knowns[k] = struct{}{}
281
		}
282
	}
283

284
	return dups, res
285
}
286

287
func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) {
288
	res := new(Result)
289

290
	if sch.Ref.String() == "" && len(sch.AllOf) == 0 { // Safeguard. We should not be able to actually get there
291
		return nil, res
292
	}
293
	var ancs []string
294

295
	schn := nm
296
	schc := &sch
297

298
	for schc.Ref.String() != "" {
299
		reso, err := s.resolveRef(&schc.Ref)
300
		if err != nil {
301
			errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
302
			return ancs, res
303
		}
304
		schc = reso
305
		schn = sch.Ref.String()
306
	}
307

308
	if schn != nm && schn != "" {
309
		if _, ok := knowns[schn]; ok {
310
			ancs = append(ancs, schn)
311
		}
312
		knowns[schn] = struct{}{}
313

314
		if len(ancs) > 0 {
315
			return ancs, res
316
		}
317
	}
318

319
	if len(schc.AllOf) > 0 {
320
		for _, chld := range schc.AllOf {
321
			if chld.Ref.String() != "" || len(chld.AllOf) > 0 {
322
				anc, rec := s.validateCircularAncestry(schn, chld, knowns)
323
				if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
324
					res.Merge(rec)
325
				}
326
				ancs = append(ancs, anc...)
327
				if len(ancs) > 0 {
328
					return ancs, res
329
				}
330
			}
331
		}
332
	}
333
	return ancs, res
334
}
335

336
func (s *SpecValidator) validateItems() *Result {
337
	// validate parameter, items, schema and response objects for presence of item if type is array
338
	res := new(Result)
339

340
	for method, pi := range s.analyzer.Operations() {
341
		for path, op := range pi {
342
			for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
343

344
				if param.TypeName() == arrayType && param.ItemsTypeName() == "" {
345
					res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
346
					continue
347
				}
348
				if param.In != swaggerBody {
349
					if param.Items != nil {
350
						items := param.Items
351
						for items.TypeName() == arrayType {
352
							if items.ItemsTypeName() == "" {
353
								res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
354
								break
355
							}
356
							items = items.Items
357
						}
358
					}
359
				} else {
360
					// In: body
361
					if param.Schema != nil {
362
						res.Merge(s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID))
363
					}
364
				}
365
			}
366

367
			var responses []spec.Response
368
			if op.Responses != nil {
369
				if op.Responses.Default != nil {
370
					responses = append(responses, *op.Responses.Default)
371
				}
372
				if op.Responses.StatusCodeResponses != nil {
373
					for _, v := range op.Responses.StatusCodeResponses {
374
						responses = append(responses, v)
375
					}
376
				}
377
			}
378

379
			for _, resp := range responses {
380
				// Response headers with array
381
				for hn, hv := range resp.Headers {
382
					if hv.TypeName() == arrayType && hv.ItemsTypeName() == "" {
383
						res.AddErrors(arrayInHeaderRequiresItemsMsg(hn, op.ID))
384
					}
385
				}
386
				if resp.Schema != nil {
387
					res.Merge(s.validateSchemaItems(*resp.Schema, "response body", op.ID))
388
				}
389
			}
390
		}
391
	}
392
	return res
393
}
394

395
// Verifies constraints on array type
396
func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) *Result {
397
	res := new(Result)
398
	if !schema.Type.Contains(arrayType) {
399
		return res
400
	}
401

402
	if schema.Items == nil || schema.Items.Len() == 0 {
403
		res.AddErrors(arrayRequiresItemsMsg(prefix, opID))
404
		return res
405
	}
406

407
	if schema.Items.Schema != nil {
408
		schema = *schema.Items.Schema
409
		if _, err := compileRegexp(schema.Pattern); err != nil {
410
			res.AddErrors(invalidItemsPatternMsg(prefix, opID, schema.Pattern))
411
		}
412

413
		res.Merge(s.validateSchemaItems(schema, prefix, opID))
414
	}
415
	return res
416
}
417

418
func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result {
419
	// Each defined operation path parameters must correspond to a named element in the API's path pattern.
420
	// (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.)
421
	res := new(Result)
422
	for _, l := range fromPath {
423
		var matched bool
424
		for _, r := range fromOperation {
425
			if l == "{"+r+"}" {
426
				matched = true
427
				break
428
			}
429
		}
430
		if !matched {
431
			res.AddErrors(noParameterInPathMsg(l))
432
		}
433
	}
434

435
	for _, p := range fromOperation {
436
		var matched bool
437
		for _, r := range fromPath {
438
			if "{"+p+"}" == r {
439
				matched = true
440
				break
441
			}
442
		}
443
		if !matched {
444
			res.AddErrors(pathParamNotInPathMsg(path, p))
445
		}
446
	}
447

448
	return res
449
}
450

451
func (s *SpecValidator) validateReferenced() *Result {
452
	var res Result
453
	res.MergeAsWarnings(s.validateReferencedParameters())
454
	res.MergeAsWarnings(s.validateReferencedResponses())
455
	res.MergeAsWarnings(s.validateReferencedDefinitions())
456
	return &res
457
}
458

459
// nolint: dupl
460
func (s *SpecValidator) validateReferencedParameters() *Result {
461
	// Each referenceable definition should have references.
462
	params := s.spec.Spec().Parameters
463
	if len(params) == 0 {
464
		return nil
465
	}
466

467
	expected := make(map[string]struct{})
468
	for k := range params {
469
		expected["#/parameters/"+jsonpointer.Escape(k)] = struct{}{}
470
	}
471
	for _, k := range s.analyzer.AllParameterReferences() {
472
		delete(expected, k)
473
	}
474

475
	if len(expected) == 0 {
476
		return nil
477
	}
478
	result := new(Result)
479
	for k := range expected {
480
		result.AddWarnings(unusedParamMsg(k))
481
	}
482
	return result
483
}
484

485
// nolint: dupl
486
func (s *SpecValidator) validateReferencedResponses() *Result {
487
	// Each referenceable definition should have references.
488
	responses := s.spec.Spec().Responses
489
	if len(responses) == 0 {
490
		return nil
491
	}
492

493
	expected := make(map[string]struct{})
494
	for k := range responses {
495
		expected["#/responses/"+jsonpointer.Escape(k)] = struct{}{}
496
	}
497
	for _, k := range s.analyzer.AllResponseReferences() {
498
		delete(expected, k)
499
	}
500

501
	if len(expected) == 0 {
502
		return nil
503
	}
504
	result := new(Result)
505
	for k := range expected {
506
		result.AddWarnings(unusedResponseMsg(k))
507
	}
508
	return result
509
}
510

511
// nolint: dupl
512
func (s *SpecValidator) validateReferencedDefinitions() *Result {
513
	// Each referenceable definition must have references.
514
	defs := s.spec.Spec().Definitions
515
	if len(defs) == 0 {
516
		return nil
517
	}
518

519
	expected := make(map[string]struct{})
520
	for k := range defs {
521
		expected["#/definitions/"+jsonpointer.Escape(k)] = struct{}{}
522
	}
523
	for _, k := range s.analyzer.AllDefinitionReferences() {
524
		delete(expected, k)
525
	}
526

527
	if len(expected) == 0 {
528
		return nil
529
	}
530

531
	result := new(Result)
532
	for k := range expected {
533
		result.AddWarnings(unusedDefinitionMsg(k))
534
	}
535
	return result
536
}
537

538
func (s *SpecValidator) validateRequiredDefinitions() *Result {
539
	// Each property listed in the required array must be defined in the properties of the model
540
	res := new(Result)
541

542
DEFINITIONS:
543
	for d, schema := range s.spec.Spec().Definitions {
544
		if schema.Required != nil { // Safeguard
545
			for _, pn := range schema.Required {
546
				red := s.validateRequiredProperties(pn, d, &schema) //#nosec
547
				res.Merge(red)
548
				if !red.IsValid() && !s.Options.ContinueOnErrors {
549
					break DEFINITIONS // there is an error, let's stop that bleeding
550
				}
551
			}
552
		}
553
	}
554
	return res
555
}
556

557
func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Schema) *Result {
558
	// Takes care of recursive property definitions, which may be nested in additionalProperties schemas
559
	res := new(Result)
560
	propertyMatch := false
561
	patternMatch := false
562
	additionalPropertiesMatch := false
563
	isReadOnly := false
564

565
	// Regular properties
566
	if _, ok := v.Properties[path]; ok {
567
		propertyMatch = true
568
		isReadOnly = v.Properties[path].ReadOnly
569
	}
570

571
	// NOTE: patternProperties are not supported in swagger. Even though, we continue validation here
572
	// We check all defined patterns: if one regexp is invalid, croaks an error
573
	for pp, pv := range v.PatternProperties {
574
		re, err := compileRegexp(pp)
575
		if err != nil {
576
			res.AddErrors(invalidPatternMsg(pp, in))
577
		} else if re.MatchString(path) {
578
			patternMatch = true
579
			if !propertyMatch {
580
				isReadOnly = pv.ReadOnly
581
			}
582
		}
583
	}
584

585
	if !(propertyMatch || patternMatch) {
586
		if v.AdditionalProperties != nil {
587
			if v.AdditionalProperties.Allows && v.AdditionalProperties.Schema == nil {
588
				additionalPropertiesMatch = true
589
			} else if v.AdditionalProperties.Schema != nil {
590
				// additionalProperties as schema are upported in swagger
591
				// recursively validates additionalProperties schema
592
				// TODO : anyOf, allOf, oneOf like in schemaPropsValidator
593
				red := s.validateRequiredProperties(path, in, v.AdditionalProperties.Schema)
594
				if red.IsValid() {
595
					additionalPropertiesMatch = true
596
					if !propertyMatch && !patternMatch {
597
						isReadOnly = v.AdditionalProperties.Schema.ReadOnly
598
					}
599
				}
600
				res.Merge(red)
601
			}
602
		}
603
	}
604

605
	if !(propertyMatch || patternMatch || additionalPropertiesMatch) {
606
		res.AddErrors(requiredButNotDefinedMsg(path, in))
607
	}
608

609
	if isReadOnly {
610
		res.AddWarnings(readOnlyAndRequiredMsg(in, path))
611
	}
612
	return res
613
}
614

615
func (s *SpecValidator) validateParameters() *Result {
616
	// - for each method, path is unique, regardless of path parameters
617
	//   e.g. GET:/petstore/{id}, GET:/petstore/{pet}, GET:/petstore are
618
	//   considered duplicate paths
619
	// - each parameter should have a unique `name` and `type` combination
620
	// - each operation should have only 1 parameter of type body
621
	// - there must be at most 1 parameter in body
622
	// - parameters with pattern property must specify valid patterns
623
	// - $ref in parameters must resolve
624
	// - path param must be required
625
	res := new(Result)
626
	rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`)
627
	for method, pi := range s.expandedAnalyzer().Operations() {
628
		methodPaths := make(map[string]map[string]string)
629
		for path, op := range pi {
630
			pathToAdd := pathHelp.stripParametersInPath(path)
631

632
			// Warn on garbled path afer param stripping
633
			if rexGarbledPathSegment.MatchString(pathToAdd) {
634
				res.AddWarnings(pathStrippedParamGarbledMsg(pathToAdd))
635
			}
636

637
			// Check uniqueness of stripped paths
638
			if _, found := methodPaths[method][pathToAdd]; found {
639

640
				// Sort names for stable, testable output
641
				if strings.Compare(path, methodPaths[method][pathToAdd]) < 0 {
642
					res.AddErrors(pathOverlapMsg(path, methodPaths[method][pathToAdd]))
643
				} else {
644
					res.AddErrors(pathOverlapMsg(methodPaths[method][pathToAdd], path))
645
				}
646
			} else {
647
				if _, found := methodPaths[method]; !found {
648
					methodPaths[method] = map[string]string{}
649
				}
650
				methodPaths[method][pathToAdd] = path // Original non stripped path
651

652
			}
653

654
			var bodyParams []string
655
			var paramNames []string
656
			var hasForm, hasBody bool
657

658
			// Check parameters names uniqueness for operation
659
			// TODO: should be done after param expansion
660
			res.Merge(s.checkUniqueParams(path, method, op))
661

662
			for _, pr := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
663
				// Validate pattern regexp for parameters with a Pattern property
664
				if _, err := compileRegexp(pr.Pattern); err != nil {
665
					res.AddErrors(invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern))
666
				}
667

668
				// There must be at most one parameter in body: list them all
669
				if pr.In == swaggerBody {
670
					bodyParams = append(bodyParams, fmt.Sprintf("%q", pr.Name))
671
					hasBody = true
672
				}
673

674
				if pr.In == "path" {
675
					paramNames = append(paramNames, pr.Name)
676
					// Path declared in path must have the required: true property
677
					if !pr.Required {
678
						res.AddErrors(pathParamRequiredMsg(op.ID, pr.Name))
679
					}
680
				}
681

682
				if pr.In == "formData" {
683
					hasForm = true
684
				}
685

686
				if !(pr.Type == numberType || pr.Type == integerType) &&
687
					(pr.Maximum != nil || pr.Minimum != nil || pr.MultipleOf != nil) {
688
					// A non-numeric parameter has validation keywords for numeric instances (number and integer)
689
					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
690
				}
691

692
				if !(pr.Type == stringType) &&
693
					// A non-string parameter has validation keywords for strings
694
					(pr.MaxLength != nil || pr.MinLength != nil || pr.Pattern != "") {
695
					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
696
				}
697

698
				if !(pr.Type == arrayType) &&
699
					// A non-array parameter has validation keywords for arrays
700
					(pr.MaxItems != nil || pr.MinItems != nil || pr.UniqueItems) {
701
					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
702
				}
703
			}
704

705
			// In:formData and In:body are mutually exclusive
706
			if hasBody && hasForm {
707
				res.AddErrors(bothFormDataAndBodyMsg(op.ID))
708
			}
709
			// There must be at most one body param
710
			// Accurately report situations when more than 1 body param is declared (possibly unnamed)
711
			if len(bodyParams) > 1 {
712
				sort.Strings(bodyParams)
713
				res.AddErrors(multipleBodyParamMsg(op.ID, bodyParams))
714
			}
715

716
			// Check uniqueness of parameters in path
717
			paramsInPath := pathHelp.extractPathParams(path)
718
			for i, p := range paramsInPath {
719
				for j, q := range paramsInPath {
720
					if p == q && i > j {
721
						res.AddErrors(pathParamNotUniqueMsg(path, p, q))
722
						break
723
					}
724
				}
725
			}
726

727
			// Warns about possible malformed params in path
728
			rexGarbledParam := mustCompileRegexp(`{.*[{}\s]+.*}`)
729
			for _, p := range paramsInPath {
730
				if rexGarbledParam.MatchString(p) {
731
					res.AddWarnings(pathParamGarbledMsg(path, p))
732
				}
733
			}
734

735
			// Match params from path vs params from params section
736
			res.Merge(s.validatePathParamPresence(path, paramsInPath, paramNames))
737
		}
738
	}
739
	return res
740
}
741

742
func (s *SpecValidator) validateReferencesValid() *Result {
743
	// each reference must point to a valid object
744
	res := new(Result)
745
	for _, r := range s.analyzer.AllRefs() {
746
		if !r.IsValidURI(s.spec.SpecFilePath()) { // Safeguard - spec should always yield a valid URI
747
			res.AddErrors(invalidRefMsg(r.String()))
748
		}
749
	}
750
	if !res.HasErrors() {
751
		// NOTE: with default settings, loads.Document.Expanded()
752
		// stops on first error. Anyhow, the expand option to continue
753
		// on errors fails to report errors at all.
754
		exp, err := s.spec.Expanded()
755
		if err != nil {
756
			res.AddErrors(unresolvedReferencesMsg(err))
757
		}
758
		s.expanded = exp
759
	}
760
	return res
761
}
762

763
func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operation) *Result {
764
	// Check for duplicate parameters declaration in param section.
765
	// Each parameter should have a unique `name` and `type` combination
766
	// NOTE: this could be factorized in analysis (when constructing the params map)
767
	// However, there are some issues with such a factorization:
768
	// - analysis does not seem to fully expand params
769
	// - param keys may be altered by x-go-name
770
	res := new(Result)
771
	pnames := make(map[string]struct{})
772

773
	if op.Parameters != nil { // Safeguard
774
		for _, ppr := range op.Parameters {
775
			var ok bool
776
			pr, red := paramHelp.resolveParam(path, method, op.ID, &ppr, s) //#nosec
777
			res.Merge(red)
778

779
			if pr != nil && pr.Name != "" { // params with empty name does no participate the check
780
				key := fmt.Sprintf("%s#%s", pr.In, pr.Name)
781

782
				if _, ok = pnames[key]; ok {
783
					res.AddErrors(duplicateParamNameMsg(pr.In, pr.Name, op.ID))
784
				}
785
				pnames[key] = struct{}{}
786
			}
787
		}
788
	}
789
	return res
790
}
791

792
// SetContinueOnErrors sets the ContinueOnErrors option for this validator.
793
func (s *SpecValidator) SetContinueOnErrors(c bool) {
794
	s.Options.ContinueOnErrors = c
795
}
796

797
// expandedAnalyzer returns expanded.Analyzer when it is available.
798
// otherwise just analyzer.
799
func (s *SpecValidator) expandedAnalyzer() *analysis.Spec {
800
	if s.expanded != nil && s.expanded.Analyzer != nil {
801
		return s.expanded.Analyzer
802
	}
803
	return s.analyzer
804
}
805

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.