Skip to content

Commit

Permalink
Cleaned up a bunch of stuff
Browse files Browse the repository at this point in the history
Consuming the validator as part of a mocking engine turned up a few usability issues. Also cleaned up formatting

Signed-off-by: Dave Shanley <dave@quobix.com>
  • Loading branch information
daveshanley committed Sep 2, 2023
1 parent ceae216 commit 90ec52a
Show file tree
Hide file tree
Showing 21 changed files with 2,733 additions and 2,490 deletions.
752 changes: 376 additions & 376 deletions errors/parameter_errors.go

Large diffs are not rendered by default.

44 changes: 22 additions & 22 deletions errors/request_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@
package errors

import (
"fmt"
"github.com/pb33f/libopenapi-validator/helpers"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
"strings"
"fmt"
"github.com/pb33f/libopenapi-validator/helpers"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
"strings"
)

func RequestContentTypeNotFound(op *v3.Operation, request *http.Request) *ValidationError {
ct := request.Header.Get(helpers.ContentTypeHeader)
var ctypes []string
for k := range op.RequestBody.Content {
ctypes = append(ctypes, k)
}
return &ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.RequestBodyContentType,
Message: fmt.Sprintf("%s operation request content type '%s' does not exist",
request.Method, ct),
Reason: fmt.Sprintf("The content type '%s' of the %s request submitted has not "+
"been defined, it's an unknown type", ct, request.Method),
SpecLine: op.RequestBody.GoLow().Content.KeyNode.Line,
SpecCol: op.RequestBody.GoLow().Content.KeyNode.Column,
Context: op,
HowToFix: fmt.Sprintf(HowToFixInvalidContentType, len(op.RequestBody.Content), strings.Join(ctypes, ", ")),
}
ct := request.Header.Get(helpers.ContentTypeHeader)
var ctypes []string
for k := range op.RequestBody.Content {
ctypes = append(ctypes, k)
}
return &ValidationError{
ValidationType: helpers.RequestBodyValidation,
ValidationSubType: helpers.RequestBodyContentType,
Message: fmt.Sprintf("%s operation request content type '%s' does not exist",
request.Method, ct),
Reason: fmt.Sprintf("The content type '%s' of the %s request submitted has not "+
"been defined, it's an unknown type", ct, request.Method),
SpecLine: op.RequestBody.GoLow().Content.KeyNode.Line,
SpecCol: op.RequestBody.GoLow().Content.KeyNode.Column,
Context: op,
HowToFix: fmt.Sprintf(HowToFixInvalidContentType, len(op.RequestBody.Content), strings.Join(ctypes, ", ")),
}
}
116 changes: 63 additions & 53 deletions errors/validation_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,95 +4,105 @@
package errors

import (
"fmt"
"github.com/santhosh-tekuri/jsonschema/v5"
"fmt"
"github.com/santhosh-tekuri/jsonschema/v5"
)

// SchemaValidationFailure is a wrapper around the jsonschema.ValidationError object, to provide a more
// user-friendly way to break down what went wrong.
type SchemaValidationFailure struct {
// Reason is a human-readable message describing the reason for the error.
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
// Reason is a human-readable message describing the reason for the error.
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`

// Location is the XPath-like location of the validation failure
Location string `json:"location,omitempty" yaml:"location,omitempty"`
// Location is the XPath-like location of the validation failure
Location string `json:"location,omitempty" yaml:"location,omitempty"`

// DeepLocation is the path to the validation failure as exposed by the jsonschema library.
DeepLocation string `json:"deepLocation,omitempty" yaml:"deepLocation,omitempty"`
// DeepLocation is the path to the validation failure as exposed by the jsonschema library.
DeepLocation string `json:"deepLocation,omitempty" yaml:"deepLocation,omitempty"`

// AbsoluteLocation is the absolute path to the validation failure as exposed by the jsonschema library.
AbsoluteLocation string `json:"absoluteLocation,omitempty" yaml:"absoluteLocation,omitempty"`
// AbsoluteLocation is the absolute path to the validation failure as exposed by the jsonschema library.
AbsoluteLocation string `json:"absoluteLocation,omitempty" yaml:"absoluteLocation,omitempty"`

// Line is the line number where the violation occurred. This may a local line number
// if the validation is a schema (only schemas are validated locally, so the line number will be relative to
// the Context object held by the ValidationError object).
Line int `json:"line,omitempty" yaml:"line,omitempty"`
// Line is the line number where the violation occurred. This may a local line number
// if the validation is a schema (only schemas are validated locally, so the line number will be relative to
// the Context object held by the ValidationError object).
Line int `json:"line,omitempty" yaml:"line,omitempty"`

// Column is the column number where the violation occurred. This may a local column number
// if the validation is a schema (only schemas are validated locally, so the column number will be relative to
// the Context object held by the ValidationError object).
Column int `json:"column,omitempty" yaml:"column,omitempty"`
// Column is the column number where the violation occurred. This may a local column number
// if the validation is a schema (only schemas are validated locally, so the column number will be relative to
// the Context object held by the ValidationError object).
Column int `json:"column,omitempty" yaml:"column,omitempty"`

// ReferenceSchema is the schema that was referenced in the validation failure.
ReferenceSchema string `json:"referenceSchema,omitempty" yaml:"referenceSchema,omitempty"`
// ReferenceSchema is the schema that was referenced in the validation failure.
ReferenceSchema string `json:"referenceSchema,omitempty" yaml:"referenceSchema,omitempty"`

// ReferenceObject is the object that was referenced in the validation failure.
ReferenceObject string `json:"referenceObject,omitempty" yaml:"referenceObject,omitempty"`
// ReferenceObject is the object that was referenced in the validation failure.
ReferenceObject string `json:"referenceObject,omitempty" yaml:"referenceObject,omitempty"`

// The original error object, which is a jsonschema.ValidationError object.
OriginalError *jsonschema.ValidationError `json:"-" yaml:"-"`
// The original error object, which is a jsonschema.ValidationError object.
OriginalError *jsonschema.ValidationError `json:"-" yaml:"-"`
}

// Error returns a string representation of the error
func (s *SchemaValidationFailure) Error() string {
return fmt.Sprintf("Reason: %s, Location: %s", s.Reason, s.Location)
return fmt.Sprintf("Reason: %s, Location: %s", s.Reason, s.Location)
}

// ValidationError is a struct that contains all the information about a validation error.
type ValidationError struct {

// Message is a human-readable message describing the error.
Message string `json:"message" yaml:"message"`
// Message is a human-readable message describing the error.
Message string `json:"message" yaml:"message"`

// Reason is a human-readable message describing the reason for the error.
Reason string `json:"reason" yaml:"reason"`
// Reason is a human-readable message describing the reason for the error.
Reason string `json:"reason" yaml:"reason"`

// ValidationType is a string that describes the type of validation that failed.
ValidationType string `json:"validationType" yaml:"validationType"`
// ValidationType is a string that describes the type of validation that failed.
ValidationType string `json:"validationType" yaml:"validationType"`

// ValidationSubType is a string that describes the subtype of validation that failed.
ValidationSubType string `json:"validationSubType" yaml:"validationSubType"`
// ValidationSubType is a string that describes the subtype of validation that failed.
ValidationSubType string `json:"validationSubType" yaml:"validationSubType"`

// SpecLine is the line number in the spec where the error occurred.
SpecLine int `json:"specLine" yaml:"specLine"`
// SpecLine is the line number in the spec where the error occurred.
SpecLine int `json:"specLine" yaml:"specLine"`

// SpecCol is the column number in the spec where the error occurred.
SpecCol int `json:"specColumn" yaml:"specColumn"`
// SpecCol is the column number in the spec where the error occurred.
SpecCol int `json:"specColumn" yaml:"specColumn"`

// HowToFix is a human-readable message describing how to fix the error.
HowToFix string `json:"howToFix" yaml:"howToFix"`
// HowToFix is a human-readable message describing how to fix the error.
HowToFix string `json:"howToFix" yaml:"howToFix"`

// SchemaValidationErrors is a slice of SchemaValidationFailure objects that describe the validation errors
// This is only populated whe the validation type is against a schema.
SchemaValidationErrors []*SchemaValidationFailure `json:"validationErrors,omitempty" yaml:"validationErrors,omitempty"`
// SchemaValidationErrors is a slice of SchemaValidationFailure objects that describe the validation errors
// This is only populated whe the validation type is against a schema.
SchemaValidationErrors []*SchemaValidationFailure `json:"validationErrors,omitempty" yaml:"validationErrors,omitempty"`

// Context is the object that the validation error occurred on. This is usually a pointer to a schema
// or a parameter object.
Context interface{} `json:"-" yaml:"-"`
// Context is the object that the validation error occurred on. This is usually a pointer to a schema
// or a parameter object.
Context interface{} `json:"-" yaml:"-"`
}

// Error returns a string representation of the error
func (v *ValidationError) Error() string {
if v.SchemaValidationErrors != nil {
return fmt.Sprintf("Error: %s, Reason: %s, Validation Errors: %s, Line: %d, Column: %d",
v.Message, v.Reason, v.SchemaValidationErrors, v.SpecLine, v.SpecCol)
} else {
return fmt.Sprintf("Error: %s, Reason: %s, Line: %d, Column: %d",
v.Message, v.Reason, v.SpecLine, v.SpecCol)
}
if v.SchemaValidationErrors != nil {
if v.SpecLine > 0 && v.SpecCol > 0 {
return fmt.Sprintf("Error: %s, Reason: %s, Validation Errors: %s, Line: %d, Column: %d",
v.Message, v.Reason, v.SchemaValidationErrors, v.SpecLine, v.SpecCol)
} else {
return fmt.Sprintf("Error: %s, Reason: %s, Validation Errors: %s",
v.Message, v.Reason, v.SchemaValidationErrors)
}
} else {
if v.SpecLine > 0 && v.SpecCol > 0 {
return fmt.Sprintf("Error: %s, Reason: %s, Line: %d, Column: %d",
v.Message, v.Reason, v.SpecLine, v.SpecCol)
} else {
return fmt.Sprintf("Error: %s, Reason: %s",
v.Message, v.Reason)
}
}
}

// IsPathMissingError returns true if the error has a ValidationType of "path" and a ValidationSubType of "missing"
func (v *ValidationError) IsPathMissingError() bool {
return v.ValidationType == "path" && v.ValidationSubType == "missing"
return v.ValidationType == "path" && v.ValidationSubType == "missing"
}
83 changes: 42 additions & 41 deletions helpers/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,46 @@
package helpers

const (
ParameterValidation = "parameter"
ParameterValidationPath = "path"
ParameterValidationQuery = "query"
ParameterValidationHeader = "header"
ParameterValidationCookie = "cookie"
RequestBodyValidation = "requestBody"
Schema = "schema"
ResponseBodyValidation = "response"
RequestBodyContentType = "contentType"
ResponseBodyResponseCode = "statusCode"
SpaceDelimited = "spaceDelimited"
PipeDelimited = "pipeDelimited"
DefaultDelimited = "default"
MatrixStyle = "matrix"
LabelStyle = "label"
Pipe = "|"
Comma = ","
Space = " "
SemiColon = ";"
Asterisk = "*"
Period = "."
Equals = "="
Integer = "integer"
Number = "number"
Slash = "/"
Object = "object"
String = "string"
Array = "array"
Boolean = "boolean"
DeepObject = "deepObject"
Header = "header"
Cookie = "cookie"
Path = "path"
Form = "form"
Query = "query"
JSONContentType = "application/json"
JSONType = "json"
ContentTypeHeader = "Content-Type"
Charset = "charset"
Boundary = "boundary"
FailSegment = "**&&FAIL&&**"
ParameterValidation = "parameter"
ParameterValidationPath = "path"
ParameterValidationQuery = "query"
ParameterValidationHeader = "header"
ParameterValidationCookie = "cookie"
RequestBodyValidation = "requestBody"
Schema = "schema"
ResponseBodyValidation = "response"
RequestBodyContentType = "contentType"
ResponseBodyResponseCode = "statusCode"
SpaceDelimited = "spaceDelimited"
PipeDelimited = "pipeDelimited"
DefaultDelimited = "default"
MatrixStyle = "matrix"
LabelStyle = "label"
Pipe = "|"
Comma = ","
Space = " "
SemiColon = ";"
Asterisk = "*"
Period = "."
Equals = "="
Integer = "integer"
Number = "number"
Slash = "/"
Object = "object"
String = "string"
Array = "array"
Boolean = "boolean"
DeepObject = "deepObject"
Header = "header"
Cookie = "cookie"
Path = "path"
Form = "form"
Query = "query"
JSONContentType = "application/json"
JSONType = "json"
ContentTypeHeader = "Content-Type"
Charset = "charset"
Boundary = "boundary"
Preferred = "preferred"
FailSegment = "**&&FAIL&&**"
)
82 changes: 41 additions & 41 deletions helpers/operation_utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,56 @@
package helpers

import (
"github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
"strings"
"github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
"strings"
)

// ExtractOperation extracts the operation from the path item based on the request method. If there is no
// matching operation found, then nil is returned.
func ExtractOperation(request *http.Request, item *v3.PathItem) *v3.Operation {
switch request.Method {
case http.MethodGet:
return item.Get
case http.MethodPost:
return item.Post
case http.MethodPut:
return item.Put
case http.MethodDelete:
return item.Delete
case http.MethodOptions:
return item.Options
case http.MethodHead:
return item.Head
case http.MethodPatch:
return item.Patch
case http.MethodTrace:
return item.Trace
}
return nil
switch request.Method {
case http.MethodGet:
return item.Get
case http.MethodPost:
return item.Post
case http.MethodPut:
return item.Put
case http.MethodDelete:
return item.Delete
case http.MethodOptions:
return item.Options
case http.MethodHead:
return item.Head
case http.MethodPatch:
return item.Patch
case http.MethodTrace:
return item.Trace
}
return nil
}

// ExtractContentType extracts the content type from the request header. First return argument is the content type
// of the request.The second (optional) argument is the charset of the request. The third (optional)
// argument is the boundary of the type (only used with forms really).
func ExtractContentType(contentType string) (string, string, string) {
var charset, boundary string
if strings.ContainsRune(contentType, ';') {
segs := strings.Split(contentType, SemiColon)
contentType = strings.TrimSpace(segs[0])
for _, v := range segs[1:] {
kv := strings.Split(v, Equals)
if len(kv) == 2 {
if strings.TrimSpace(strings.ToLower(kv[0])) == Charset {
charset = strings.TrimSpace(kv[1])
}
if strings.TrimSpace(strings.ToLower(kv[0])) == Boundary {
boundary = strings.TrimSpace(kv[1])
}
}
}
} else {
contentType = strings.TrimSpace(contentType)
}
return contentType, charset, boundary
var charset, boundary string
if strings.ContainsRune(contentType, ';') {
segs := strings.Split(contentType, SemiColon)
contentType = strings.TrimSpace(segs[0])
for _, v := range segs[1:] {
kv := strings.Split(v, Equals)
if len(kv) == 2 {
if strings.TrimSpace(strings.ToLower(kv[0])) == Charset {
charset = strings.TrimSpace(kv[1])
}
if strings.TrimSpace(strings.ToLower(kv[0])) == Boundary {
boundary = strings.TrimSpace(kv[1])
}
}
}
} else {
contentType = strings.TrimSpace(contentType)
}
return contentType, charset, boundary
}
Loading

0 comments on commit 90ec52a

Please sign in to comment.