forked from forgejo/forgejo
Use vendored go-swagger (#8087)
* Use vendored go-swagger * vendor go-swagger * revert un wanteed change * remove un-needed GO111MODULE * Update Makefile Co-Authored-By: techknowlogick <matti@mdranta.net>
This commit is contained in:
parent
4cb1bdddc8
commit
9fe4437bda
686 changed files with 143379 additions and 17 deletions
777
vendor/github.com/go-openapi/validate/spec.go
generated
vendored
Normal file
777
vendor/github.com/go-openapi/validate/spec.go
generated
vendored
Normal file
|
@ -0,0 +1,777 @@
|
|||
// 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue