Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 42 additions & 54 deletions default_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
package validate

import (
"fmt"
"strings"

"github.com/go-openapi/spec"
)

Expand Down Expand Up @@ -44,28 +41,16 @@ func (d *defaultValidator) resetVisited() {
}
}

func isVisited(path string, visitedSchemas map[string]struct{}) bool {
_, found := visitedSchemas[path]
func isVisited(path pathSegments, visitedSchemas map[string]struct{}) bool {
_, found := visitedSchemas[path.pointer()]
if found {
return true
}

// search for overlapping paths
var (
parent string
suffix string
)
const backtrackFromEnd = 2
for i := len(path) - backtrackFromEnd; i >= 0; i-- {
r := path[i]
if r != '.' {
continue
}

parent = path[0:i]
suffix = path[i+1:]

if strings.HasSuffix(parent, suffix) {
// search for overlapping paths: a trailing run of tokens that already
// appears at the end of what leads to it means we are going in circles.
for i := 1; i < len(path); i++ {
if path[:i].hasSuffix(path[i:]) {
return true
}
}
Expand All @@ -74,12 +59,12 @@ func isVisited(path string, visitedSchemas map[string]struct{}) bool {
}

// beingVisited asserts a schema is being visited.
func (d *defaultValidator) beingVisited(path string) {
d.visitedSchemas[path] = struct{}{}
func (d *defaultValidator) beingVisited(path pathSegments) {
d.visitedSchemas[path.pointer()] = struct{}{}
}

// isVisited tells if a path has already been visited.
func (d *defaultValidator) isVisited(path string) bool {
func (d *defaultValidator) isVisited(path pathSegments) bool {
return isVisited(path, d.visitedSchemas)
}

Expand All @@ -96,7 +81,7 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
// parameters
for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
if param.Default != nil && param.Required {
res.AddWarnings(requiredHasDefaultMsg(param.Name, param.In))
res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), requiredHasDefaultMsg(param.Name, param.In))
}

// reset explored schemas to get depth-first recursive-proof exploration
Expand All @@ -108,7 +93,7 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
// check param default value is valid
red := newParamValidator(&param, s.KnownFormats, d.schemaOptions).Validate(param.Default) //#nosec
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In))
res.Merge(red)
} else if red.wantsRedeemOnMerge {
pools.poolOfResults.RedeemResult(red)
Expand All @@ -117,9 +102,9 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {

// Recursively follows Items and Schemas
if param.Items != nil {
red := d.validateDefaultValueItemsAgainstSchema(param.Name, param.In, &param, param.Items) //#nosec
red := d.validateDefaultValueItemsAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, &param, param.Items) //#nosec
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueItemsDoesNotValidateMsg(param.Name, param.In))
res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueItemsDoesNotValidateMsg(param.Name, param.In))
res.Merge(red)
} else if red.wantsRedeemOnMerge {
pools.poolOfResults.RedeemResult(red)
Expand All @@ -128,9 +113,9 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {

if param.Schema != nil {
// Validate default value against schema
red := d.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema)
red := d.validateDefaultValueSchemaAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, param.Schema)
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In))
res.Merge(red)
} else if red.wantsRedeemOnMerge {
pools.poolOfResults.RedeemResult(red)
Expand All @@ -141,31 +126,33 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
if op.Responses != nil {
if op.Responses.Default != nil {
// Same constraint on default Response
res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, 0, op.ID))
res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, method, 0, op.ID))
}
// Same constraint on regular Responses
if op.Responses.StatusCodeResponses != nil { // Safeguard
for code, r := range op.Responses.StatusCodeResponses {
res.Merge(d.validateDefaultInResponse(&r, "response", path, code, op.ID)) //#nosec
res.Merge(d.validateDefaultInResponse(&r, "response", path, method, code, op.ID)) //#nosec
}
}
} else if op.ID != "" {
// Empty op.ID means there is no meaningful operation: no need to report a specific message
res.AddErrors(noValidResponseMsg(op.ID))
res.addErrorsAt(operationPath(path, method), noValidResponseMsg(op.ID))
}
}
}
if s.spec.Spec().Definitions != nil { // Safeguard
// reset explored schemas to get depth-first recursive-proof exploration
d.resetVisited()
for nm, sch := range s.spec.Spec().Definitions {
res.Merge(d.validateDefaultValueSchemaAgainstSchema("definitions."+nm, "body", &sch)) //#nosec
res.Merge(d.validateDefaultValueSchemaAgainstSchema(newPathSegments(swaggerDefinitions, nm), "body", &sch)) //#nosec
}
}
return res
}

func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result {
func (d *defaultValidator) validateDefaultInResponse(
resp *spec.Response, responseType, path, method string, responseCode int, operationID string,
) *Result {
s := d.SpecValidator

response, res := responseHelp.expandResponseRef(resp, path, s)
Expand All @@ -183,7 +170,7 @@ func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, respon
if h.Default != nil {
red := newHeaderValidator(nm, &h, s.KnownFormats, d.schemaOptions).Validate(h.Default) //#nosec
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
res.Merge(red)
} else if red.wantsRedeemOnMerge {
pools.poolOfResults.RedeemResult(red)
Expand All @@ -192,17 +179,17 @@ func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, respon

// Headers have inline definition, like params
if h.Items != nil {
red := d.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items) //#nosec
red := d.validateDefaultValueItemsAgainstSchema(responseHeaderPath(path, method, responseCodeAsStr, nm), "header", &h, h.Items) //#nosec
if red.HasErrorsOrWarnings() {
res.AddErrors(defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
res.Merge(red)
} else if red.wantsRedeemOnMerge {
pools.poolOfResults.RedeemResult(red)
}
}

if _, err := compileRegexp(h.Pattern); err != nil {
res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
}

// Headers don't have schema
Expand All @@ -212,10 +199,11 @@ func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, respon
// reset explored schemas to get depth-first recursive-proof exploration
d.resetVisited()

red := d.validateDefaultValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema)
red := d.validateDefaultValueSchemaAgainstSchema(
responsePath(path, method, responseCodeAsStr).structuralChild(jsonSchema), "response", response.Schema)
if red.HasErrorsOrWarnings() {
// Additional message to make sure the context of the error is not lost
res.AddErrors(defaultValueInDoesNotValidateMsg(operationID, responseName))
res.addErrorsAt(responsePath(path, method, responseCodeAsStr), defaultValueInDoesNotValidateMsg(operationID, responseName))
res.Merge(red)
} else if red.wantsRedeemOnMerge {
pools.poolOfResults.RedeemResult(red)
Expand All @@ -224,7 +212,7 @@ func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, respon
return res
}

func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path pathSegments, in string, schema *spec.Schema) *Result {
if schema == nil || d.isVisited(path) {
// Avoids recursing if we are already done with that check
return nil
Expand All @@ -235,47 +223,47 @@ func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in stri

if schema.Default != nil {
res.Merge(
newSchemaValidator(schema, s.spec.Spec(), path+".default", s.KnownFormats, d.schemaOptions).Validate(schema.Default),
newSchemaValidator(schema, s.spec.Spec(), path.child(jsonDefault), s.KnownFormats, d.schemaOptions).Validate(schema.Default),
)
}
if schema.Items != nil {
if schema.Items.Schema != nil {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".items.default", in, schema.Items.Schema))
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonItems), in, schema.Items.Schema))
}
// Multiple schemas in items
if schema.Items.Schemas != nil { // Safeguard
for i, sch := range schema.Items.Schemas {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].default", path, i), in, &sch)) //#nosec
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonItems).item(i), in, &sch)) //#nosec
}
}
}
if _, err := compileRegexp(schema.Pattern); err != nil {
res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern))
res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, schema.Pattern))
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
// NOTE: we keep validating values, even though additionalItems is not supported by Swagger 2.0 (and 3.0 as well)
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalItems", in, schema.AdditionalItems.Schema))
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAdditionalItems), in, schema.AdditionalItems.Schema))
}
for propName, prop := range schema.Properties {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.structuralChild(jsonProperties).child(propName), in, &prop)) //#nosec
}
for propName, prop := range schema.PatternProperties {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.structuralChild(jsonPatternProperties).child(propName), in, &prop)) //#nosec
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalProperties", in, schema.AdditionalProperties.Schema))
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAdditionalProperties), in, schema.AdditionalProperties.Schema))
}
if schema.AllOf != nil {
for i, aoSch := range schema.AllOf {
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) //#nosec
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAllOf).item(i), in, &aoSch)) //#nosec
}
}
return res
}

// NOTE: Temporary duplicated code. Need to refactor with examples

func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in string, root any, items *spec.Items) *Result {
func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path pathSegments, in string, root any, items *spec.Items) *Result {
res := pools.poolOfResults.BorrowResult()
s := d.SpecValidator
if items != nil {
Expand All @@ -285,10 +273,10 @@ func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in strin
)
}
if items.Items != nil {
res.Merge(d.validateDefaultValueItemsAgainstSchema(path+"[0].default", in, root, items.Items))
res.Merge(d.validateDefaultValueItemsAgainstSchema(path.item(0), in, root, items.Items))
}
if _, err := compileRegexp(items.Pattern); err != nil {
res.AddErrors(invalidPatternInMsg(path, in, items.Pattern))
res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, items.Pattern))
}
}
return res
Expand Down
Loading
Loading