Skip to content
Open
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
164 changes: 83 additions & 81 deletions requests/validate_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package requests
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -22,7 +23,7 @@ import (

"github.com/pb33f/libopenapi-validator/cache"
"github.com/pb33f/libopenapi-validator/config"
"github.com/pb33f/libopenapi-validator/errors"
liberrors "github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi-validator/schema_validation"
"github.com/pb33f/libopenapi-validator/strict"
Expand Down Expand Up @@ -133,23 +134,23 @@ func readAndResetRequestBody(request *http.Request) []byte {
// ValidateRequestSchema will validate a http.Request pointer against a schema.
// If validation fails, it will return a list of validation errors as the second return value.
// The schema will be stored and reused from cache if available, otherwise it will be compiled on each call.
func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.ValidationError) {
func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*liberrors.ValidationError) {
validationOptions := config.NewValidationOptions(input.Options...)
var validationErrors []*errors.ValidationError
var validationErrors []*liberrors.ValidationError
var renderedSchema, jsonSchema []byte
var referenceSchema string
var compiledSchema *jsonschema.Schema
var cachedNode *yaml.Node

if input.Schema == nil {
return false, []*errors.ValidationError{{
return false, []*liberrors.ValidationError{{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.Schema,
Message: "schema is nil",
Reason: "The schema to validate against is nil",
}}
} else if input.Schema.GoLow() == nil {
return false, []*errors.ValidationError{{
return false, []*liberrors.ValidationError{{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.Schema,
Message: "schema cannot be rendered",
Expand Down Expand Up @@ -177,11 +178,11 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V

// If rendering failed (e.g., circular reference), return the render error
if renderErr != nil {
violation := &errors.SchemaValidationFailure{
violation := &liberrors.SchemaValidationFailure{
Reason: renderErr.Error(),
ReferenceSchema: referenceSchema,
}
validationErrors = append(validationErrors, &errors.ValidationError{
validationErrors = append(validationErrors, &liberrors.ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.Schema,
Message: fmt.Sprintf("%s request body for '%s' failed schema rendering",
Expand All @@ -190,8 +191,8 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
renderErr.Error()),
SpecLine: 1,
SpecCol: 0,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: errors.HowToFixInvalidRenderedSchema,
SchemaValidationErrors: []*liberrors.SchemaValidationFailure{violation},
HowToFix: liberrors.HowToFixInvalidRenderedSchema,
Context: referenceSchema,
})
return false, validationErrors
Expand All @@ -208,7 +209,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
input.Version,
)
if err != nil {
validationErrors = append(validationErrors, &errors.ValidationError{
validationErrors = append(validationErrors, &liberrors.ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.Schema,
Message: fmt.Sprintf("%s request body for '%s' failed schema compilation",
Expand Down Expand Up @@ -245,15 +246,15 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
err := json.Unmarshal(requestBody, &decodedObj)
if err != nil {
// cannot decode the request body, so it's not valid
validationErrors = append(validationErrors, &errors.ValidationError{
validationErrors = append(validationErrors, &liberrors.ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.Schema,
Message: fmt.Sprintf("%s request body for '%s' failed to validate schema",
request.Method, request.URL.Path),
Reason: fmt.Sprintf("The request body cannot be decoded: %s", err.Error()),
SpecLine: 1,
SpecCol: 0,
HowToFix: errors.HowToFixInvalidSchema,
HowToFix: liberrors.HowToFixInvalidSchema,
Context: schema,
})
return false, validationErrors
Expand Down Expand Up @@ -281,15 +282,15 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
}
}

validationErrors = append(validationErrors, &errors.ValidationError{
validationErrors = append(validationErrors, &liberrors.ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.Schema,
Message: fmt.Sprintf("%s request body is empty for '%s'",
request.Method, request.URL.Path),
Reason: "The request body is empty but there is a schema defined",
SpecLine: line,
SpecCol: col,
HowToFix: errors.HowToFixInvalidSchema,
HowToFix: liberrors.HowToFixInvalidSchema,
Context: schema,
})
return false, validationErrors
Expand All @@ -298,80 +299,81 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
// validate the object against the schema
scErrs := compiledSchema.Validate(decodedObj)
if scErrs != nil {

jk := scErrs.(*jsonschema.ValidationError)

// flatten the validationErrors
schFlatErrs := jk.BasicOutput().Errors
var schemaValidationErrors []*errors.SchemaValidationFailure

// Use cached node if available, otherwise parse
renderedNode := cachedNode
if renderedNode == nil {
renderedNode = new(yaml.Node)
_ = yaml.Unmarshal(renderedSchema, renderedNode)
}
for q := range schFlatErrs {
er := schFlatErrs[q]

errMsg := er.Error.Kind.LocalizedString(message.NewPrinter(language.Tag{}))

if er.KeywordLocation == "" || helpers.IgnoreRegex.MatchString(errMsg) {
continue // ignore this error, it's useless tbh, utter noise.
var jk *jsonschema.ValidationError
var schemaValidationErrors []*liberrors.SchemaValidationFailure

if errors.As(scErrs, &jk) {
// flatten the validationErrors
schFlatErrs := jk.BasicOutput().Errors

// Use cached node if available, otherwise parse
renderedNode := cachedNode
if renderedNode == nil {
renderedNode = new(yaml.Node)
_ = yaml.Unmarshal(renderedSchema, renderedNode)
}
if er.Error != nil {
for q := range schFlatErrs {
er := schFlatErrs[q]

// locate the violated property in the schema
var located *yaml.Node
if len(renderedNode.Content) > 0 {
located = schema_validation.LocateSchemaPropertyNodeByJSONPath(renderedNode.Content[0], er.KeywordLocation)
}
errMsg := er.Error.Kind.LocalizedString(message.NewPrinter(language.Tag{}))

// extract the element specified by the instance
val := instanceLocationRegex.FindStringSubmatch(er.InstanceLocation)
var referenceObject string
if er.KeywordLocation == "" || helpers.IgnoreRegex.MatchString(errMsg) {
continue // ignore this error, it's useless tbh, utter noise.
}
if er.Error != nil {

if len(val) > 0 {
referenceIndex, _ := strconv.Atoi(val[1])
if reflect.ValueOf(decodedObj).Type().Kind() == reflect.Slice {
found := decodedObj.([]any)[referenceIndex]
recoded, _ := json.MarshalIndent(found, "", " ")
referenceObject = string(recoded)
// locate the violated property in the schema
var located *yaml.Node
if len(renderedNode.Content) > 0 {
located = schema_validation.LocateSchemaPropertyNodeByJSONPath(renderedNode.Content[0], er.KeywordLocation)
}
}
if referenceObject == "" {
referenceObject = string(requestBody)
}

errMsg := er.Error.Kind.LocalizedString(message.NewPrinter(language.Tag{}))
// extract the element specified by the instance
val := instanceLocationRegex.FindStringSubmatch(er.InstanceLocation)
var referenceObject string

violation := &errors.SchemaValidationFailure{
Reason: errMsg,
FieldName: helpers.ExtractFieldNameFromStringLocation(er.InstanceLocation),
FieldPath: helpers.ExtractJSONPathFromStringLocation(er.InstanceLocation),
InstancePath: helpers.ConvertStringLocationToPathSegments(er.InstanceLocation),
KeywordLocation: er.KeywordLocation,
ReferenceSchema: referenceSchema,
ReferenceObject: referenceObject,
OriginalJsonSchemaError: jk,
}
// if we have a location within the schema, add it to the error
if located != nil {

line := located.Line
// if the located node is a map or an array, then the actual human interpretable
// line on which the violation occurred is the line of the key, not the value.
if located.Kind == yaml.MappingNode || located.Kind == yaml.SequenceNode {
if line > 0 {
line--
if len(val) > 0 {
referenceIndex, _ := strconv.Atoi(val[1])
if reflect.ValueOf(decodedObj).Type().Kind() == reflect.Slice {
found := decodedObj.([]any)[referenceIndex]
recoded, _ := json.MarshalIndent(found, "", " ")
referenceObject = string(recoded)
}
}
if referenceObject == "" {
referenceObject = string(requestBody)
}

// location of the violation within the rendered schema.
violation.Line = line
violation.Column = located.Column
errMsg := er.Error.Kind.LocalizedString(message.NewPrinter(language.Tag{}))

violation := &liberrors.SchemaValidationFailure{
Reason: errMsg,
FieldName: helpers.ExtractFieldNameFromStringLocation(er.InstanceLocation),
FieldPath: helpers.ExtractJSONPathFromStringLocation(er.InstanceLocation),
InstancePath: helpers.ConvertStringLocationToPathSegments(er.InstanceLocation),
KeywordLocation: er.KeywordLocation,
ReferenceSchema: referenceSchema,
ReferenceObject: referenceObject,
OriginalJsonSchemaError: jk,
}
// if we have a location within the schema, add it to the error
if located != nil {

line := located.Line
// if the located node is a map or an array, then the actual human interpretable
// line on which the violation occurred is the line of the key, not the value.
if located.Kind == yaml.MappingNode || located.Kind == yaml.SequenceNode {
if line > 0 {
line--
}
}

// location of the violation within the rendered schema.
violation.Line = line
violation.Column = located.Column
}
schemaValidationErrors = append(schemaValidationErrors, violation)
}
schemaValidationErrors = append(schemaValidationErrors, violation)
}
}

Expand All @@ -383,7 +385,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
}

// add the error to the list
validationErrors = append(validationErrors, &errors.ValidationError{
validationErrors = append(validationErrors, &liberrors.ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.Schema,
Message: fmt.Sprintf("%s request body for '%s' failed to validate schema",
Expand All @@ -393,7 +395,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
SpecLine: line,
SpecCol: col,
SchemaValidationErrors: schemaValidationErrors,
HowToFix: errors.HowToFixInvalidSchema,
HowToFix: liberrors.HowToFixInvalidSchema,
Context: schema,
})
}
Expand All @@ -418,14 +420,14 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
switch undeclared.Type {
case strict.TypeReadOnlyProperty:
validationErrors = append(validationErrors,
errors.ReadOnlyPropertyError(
liberrors.ReadOnlyPropertyError(
undeclared.Path, undeclared.Name, undeclared.Value,
request.URL.Path, request.Method,
undeclared.SpecLine, undeclared.SpecCol,
))
default:
validationErrors = append(validationErrors,
errors.UndeclaredPropertyError(
liberrors.UndeclaredPropertyError(
undeclared.Path,
undeclared.Name,
undeclared.Value,
Expand Down
Loading
Loading