| // 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{}) (errs *Result, warnings *Result) { |
| var sd *loads.Document |
| errs = new(Result) |
| |
| switch v := data.(type) { |
| case *loads.Document: |
| sd = v |
| } |
| if sd == nil { |
| errs.AddErrors(invalidDocumentMsg()) |
| return |
| } |
| s.spec = sd |
| s.analyzer = analysis.New(sd.Spec()) |
| |
| warnings = new(Result) |
| |
| // Swagger schema validator |
| schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats) |
| 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 // 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 // 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 // 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 |
| } |
| |
| 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 |
| res := new(Result) |
| known := make(map[string]int) |
| for _, v := range s.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() == "array" && param.ItemsTypeName() == "" { |
| res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID)) |
| continue |
| } |
| if param.In != "body" { |
| if param.Items != nil { |
| items := param.Items |
| for items.TypeName() == "array" { |
| 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() == "array" && 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("array") { |
| 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 |
| } |
| |
| 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() { |
| if _, ok := expected[k]; ok { |
| delete(expected, k) |
| } |
| } |
| |
| if len(expected) == 0 { |
| return nil |
| } |
| result := new(Result) |
| for k := range expected { |
| result.AddWarnings(unusedParamMsg(k)) |
| } |
| return result |
| } |
| |
| 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() { |
| if _, ok := expected[k]; ok { |
| delete(expected, k) |
| } |
| } |
| |
| if len(expected) == 0 { |
| return nil |
| } |
| result := new(Result) |
| for k := range expected { |
| result.AddWarnings(unusedResponseMsg(k)) |
| } |
| return result |
| } |
| |
| 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() { |
| if _, ok := expected[k]; ok { |
| 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) |
| 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) |
| if pi != nil { // Safeguard |
| 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 == "body" { |
| 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 |
| } |
| } |
| |
| // 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) |
| 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 |
| } |