Skip to content

Commit

Permalink
Enhance validation configuration options to include assertions for 'f…
Browse files Browse the repository at this point in the history
…ormat' and 'content'
  • Loading branch information
JemDay committed Jan 3, 2025
1 parent 3d64c93 commit ea021d4
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 13 deletions.
23 changes: 21 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import "github.com/santhosh-tekuri/jsonschema/v6"
//
// Generally fluent With... style functions are used to establish the desired behavior.
type ValidationOptions struct {
RegexEngine jsonschema.RegexpEngine
RegexEngine jsonschema.RegexpEngine
FormatAssertions bool
ContentAssertions bool
}

// Option Enables an 'Options pattern' approach
Expand All @@ -15,7 +17,10 @@ type Option func(*ValidationOptions)
// NewValidationOptions creates a new ValidationOptions instance with default values.
func NewValidationOptions(opts ...Option) *ValidationOptions {
// Create the set of default values
o := &ValidationOptions{}
o := &ValidationOptions{
FormatAssertions: false,
ContentAssertions: false,
}

// Apply any supplied overrides
for _, opt := range opts {
Expand All @@ -32,3 +37,17 @@ func WithRegexEngine(engine jsonschema.RegexpEngine) Option {
o.RegexEngine = engine
}
}

// WithFormatAssertions enables checks for 'format' assertions (such as date, date-time, uuid, etc)
func WithFormatAssertions() Option {
return func(o *ValidationOptions) {
o.FormatAssertions = true
}
}

// WithContentAssertions enables checks for contentType, contentEncoding, etc
func WithContentAssertions() Option {
return func(o *ValidationOptions) {
o.ContentAssertions = true
}

Check warning on line 52 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L49-L52

Added lines #L49 - L52 were not covered by tests
}
55 changes: 55 additions & 0 deletions helpers/schema_compiler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package helpers

import (
"bytes"
"fmt"

"github.com/santhosh-tekuri/jsonschema/v6"

"github.com/pb33f/libopenapi-validator/config"
)

// ConfigureCompiler configures a JSON Schema compiler with the desired behavior.
func ConfigureCompiler(c *jsonschema.Compiler, o *config.ValidationOptions) {
// nil is the default so this is OK.
c.UseRegexpEngine(o.RegexEngine)

// Enable Format assertions if required.
if o.FormatAssertions {
c.AssertFormat()
}

Check warning on line 20 in helpers/schema_compiler.go

View check run for this annotation

Codecov / codecov/patch

helpers/schema_compiler.go#L13-L20

Added lines #L13 - L20 were not covered by tests

// Content Assertions
if o.ContentAssertions {
c.AssertContent()
}

Check warning on line 25 in helpers/schema_compiler.go

View check run for this annotation

Codecov / codecov/patch

helpers/schema_compiler.go#L23-L25

Added lines #L23 - L25 were not covered by tests
}

// NewCompilerWithOptions mints a new JSON schema compiler with custom configuration.
func NewCompilerWithOptions(o *config.ValidationOptions) *jsonschema.Compiler {
// Build it
c := jsonschema.NewCompiler()

// Configure it
ConfigureCompiler(c, o)

// Return it
return c

Check warning on line 37 in helpers/schema_compiler.go

View check run for this annotation

Codecov / codecov/patch

helpers/schema_compiler.go#L29-L37

Added lines #L29 - L37 were not covered by tests
}

// NewCompiledSchema establishes a programmatic representation of a JSON Schema document that is used for validation.
func NewCompiledSchema(name string, jsonSchema []byte, o *config.ValidationOptions) *jsonschema.Schema {
// Establish a compiler with the desired configuration
compiler := NewCompilerWithOptions(o)
compiler.UseLoader(NewCompilerLoader())

// Decode the JSON Schema into a JSON blob.
decodedSchema, _ := jsonschema.UnmarshalJSON(bytes.NewReader(jsonSchema))
_ = compiler.AddResource(fmt.Sprintf("%s.json", name), decodedSchema)

// Try to compile it.
jsch, _ := compiler.Compile(fmt.Sprintf("%s.json", name))

// Done.
return jsch

Check warning on line 54 in helpers/schema_compiler.go

View check run for this annotation

Codecov / codecov/patch

helpers/schema_compiler.go#L41-L54

Added lines #L41 - L54 were not covered by tests
}
2 changes: 2 additions & 0 deletions parameters/path_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p
p.Name,
helpers.ParameterValidation,
helpers.ParameterValidationPath,
v.options,
)...)

case helpers.Integer, helpers.Number:
Expand All @@ -161,6 +162,7 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p
p.Name,
helpers.ParameterValidation,
helpers.ParameterValidationPath,
v.options,
)...)

case helpers.Boolean:
Expand Down
1 change: 1 addition & 0 deletions parameters/query_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,5 +252,6 @@ func (v *paramValidator) validateSimpleParam(sch *base.Schema, rawParam string,
parameter.Name,
helpers.ParameterValidation,
helpers.ParameterValidationQuery,
v.options,
)
}
105 changes: 105 additions & 0 deletions parameters/query_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/pb33f/libopenapi-validator/config"
"github.com/pb33f/libopenapi-validator/paths"
)

Expand Down Expand Up @@ -669,6 +670,110 @@ paths:
assert.Len(t, errors, 0)
}

func TestNewValidator_QueryParamValidDateFormat(t *testing.T) {
spec := `openapi: 3.1.0
paths:
/a/fishy/on/a/dishy:
get:
parameters:
- name: fishy
in: query
required: true
schema:
type: string
format: date`

doc, _ := libopenapi.NewDocument([]byte(spec))

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model, config.WithFormatAssertions())

request, _ := http.NewRequest(http.MethodGet, "https://things.com/a/fishy/on/a/dishy?fishy=2024-12-25", nil)

valid, errors := v.ValidateQueryParams(request)
assert.True(t, valid)
assert.Len(t, errors, 0)
}

func TestNewValidator_QueryParamInvalidDateFormat(t *testing.T) {
spec := `openapi: 3.1.0
paths:
/a/fishy/on/a/dishy:
get:
parameters:
- name: fishy
in: query
required: true
schema:
type: string
format: date`

doc, _ := libopenapi.NewDocument([]byte(spec))

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model, config.WithFormatAssertions())

request, _ := http.NewRequest(http.MethodGet, "https://things.com/a/fishy/on/a/dishy?fishy=12/25/2024", nil)

valid, errors := v.ValidateQueryParams(request)
assert.False(t, valid)
assert.Len(t, errors, 1)
}

func TestNewValidator_QueryParamValidDateTimeFormat(t *testing.T) {
spec := `openapi: 3.1.0
paths:
/a/fishy/on/a/dishy:
get:
parameters:
- name: fishy
in: query
required: true
schema:
type: string
format: date-time`

doc, _ := libopenapi.NewDocument([]byte(spec))

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model, config.WithFormatAssertions())

request, _ := http.NewRequest(http.MethodGet, "https://things.com/a/fishy/on/a/dishy?fishy=2024-12-25T13:42:42Z", nil)

valid, errors := v.ValidateQueryParams(request)
assert.True(t, valid)
assert.Len(t, errors, 0)
}

func TestNewValidator_QueryParamInvalidDateTimeFormat(t *testing.T) {
spec := `openapi: 3.1.0
paths:
/a/fishy/on/a/dishy:
get:
parameters:
- name: fishy
in: query
required: true
schema:
type: string
format: date-time`

doc, _ := libopenapi.NewDocument([]byte(spec))

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model, config.WithFormatAssertions())

request, _ := http.NewRequest(http.MethodGet, "https://things.com/a/fishy/on/a/dishy?fishy=2024-12-25", nil)

valid, errors := v.ValidateQueryParams(request)
assert.False(t, valid)
assert.Len(t, errors, 1)
}

func TestNewValidator_QueryParamValidTypeArrayString(t *testing.T) {
spec := `openapi: 3.1.0
paths:
Expand Down
14 changes: 3 additions & 11 deletions parameters/validate_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

stdError "errors"

"github.com/pb33f/libopenapi-validator/config"
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
)
Expand All @@ -30,8 +31,9 @@ func ValidateSingleParameterSchema(
name string,
validationType string,
subValType string,
o *config.ValidationOptions,
) (validationErrors []*errors.ValidationError) {
jsch := compileSchema(name, buildJsonRender(schema))
jsch := helpers.NewCompiledSchema(name, buildJsonRender(schema), o)

scErrs := jsch.Validate(rawObject)
var werras *jsonschema.ValidationError
Expand All @@ -41,16 +43,6 @@ func ValidateSingleParameterSchema(
return validationErrors
}

// compileSchema create a new json schema compiler and add the schema to it.
func compileSchema(name string, jsonSchema []byte) *jsonschema.Schema {
compiler := jsonschema.NewCompiler()
compiler.UseLoader(helpers.NewCompilerLoader())
decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(jsonSchema))) // decode the schema into a json blob
_ = compiler.AddResource(fmt.Sprintf("%s.json", name), decodedSchema)
jsch, _ := compiler.Compile(fmt.Sprintf("%s.json", name))
return jsch
}

// buildJsonRender build a JSON render of the schema.
func buildJsonRender(schema *base.Schema) []byte {
renderedSchema, _ := schema.Render()
Expand Down

0 comments on commit ea021d4

Please sign in to comment.