From ea021d43fc2495fa32f26b7c7a82ed7deb1e8d20 Mon Sep 17 00:00:00 2001 From: Jem Day Date: Fri, 3 Jan 2025 14:39:06 -0800 Subject: [PATCH 1/8] Enhance validation configuration options to include assertions for 'format' and 'content' --- config/config.go | 23 +++++- helpers/schema_compiler.go | 55 +++++++++++++++ parameters/path_parameters.go | 2 + parameters/query_parameters.go | 1 + parameters/query_parameters_test.go | 105 ++++++++++++++++++++++++++++ parameters/validate_parameter.go | 14 +--- 6 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 helpers/schema_compiler.go diff --git a/config/config.go b/config/config.go index 1b10724..f91e5a7 100644 --- a/config/config.go +++ b/config/config.go @@ -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 @@ -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 { @@ -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 + } +} diff --git a/helpers/schema_compiler.go b/helpers/schema_compiler.go new file mode 100644 index 0000000..188a743 --- /dev/null +++ b/helpers/schema_compiler.go @@ -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() + } + + // Content Assertions + if o.ContentAssertions { + c.AssertContent() + } +} + +// 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 +} + +// 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 +} diff --git a/parameters/path_parameters.go b/parameters/path_parameters.go index 41ae277..f424ca5 100644 --- a/parameters/path_parameters.go +++ b/parameters/path_parameters.go @@ -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: @@ -161,6 +162,7 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p p.Name, helpers.ParameterValidation, helpers.ParameterValidationPath, + v.options, )...) case helpers.Boolean: diff --git a/parameters/query_parameters.go b/parameters/query_parameters.go index 940e068..f04d56f 100644 --- a/parameters/query_parameters.go +++ b/parameters/query_parameters.go @@ -252,5 +252,6 @@ func (v *paramValidator) validateSimpleParam(sch *base.Schema, rawParam string, parameter.Name, helpers.ParameterValidation, helpers.ParameterValidationQuery, + v.options, ) } diff --git a/parameters/query_parameters_test.go b/parameters/query_parameters_test.go index 3a83870..31d29fe 100644 --- a/parameters/query_parameters_test.go +++ b/parameters/query_parameters_test.go @@ -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" ) @@ -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: diff --git a/parameters/validate_parameter.go b/parameters/validate_parameter.go index 5a5eb8b..ef5ceef 100644 --- a/parameters/validate_parameter.go +++ b/parameters/validate_parameter.go @@ -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" ) @@ -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 @@ -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() From b839f3ccc3394356968cd92fae4c5e69a343cd2e Mon Sep 17 00:00:00 2001 From: Jem Day Date: Mon, 6 Jan 2025 10:03:04 -0800 Subject: [PATCH 2/8] - Propogate errors from helper functions - Updated to cover all situations where JSON schema compilation occurs. - A few new Unts tests to improve coverage. --- helpers/schema_compiler_test.go | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 helpers/schema_compiler_test.go diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go new file mode 100644 index 0000000..b20675c --- /dev/null +++ b/helpers/schema_compiler_test.go @@ -0,0 +1,62 @@ +package helpers + +import ( + "testing" + + "github.com/pb33f/libopenapi-validator/config" + "github.com/stretchr/testify/require" +) + +// A few simple JSON Schemas +const stringSchema = `{ + "type": "string", + "format": "date", + "minLength": 10 +}` + +const objectSchema = `{ + "type": "object", + "title" : "Fish", + "properties" : { + "name" : { + "type": "string", + "description": "The given name of the fish" + }, + "species" : { + "type" : "string", + "enum" : [ "OTHER", "GUPPY", "PIKE", "BASS" ] + } + } +}` + +func Test_SchemaWithNilOptions(t *testing.T) { + jsch, err := NewCompiledSchema("test", []byte(stringSchema), nil) + + require.Nil(t, err, "Failed to compile Schema: %v", err) + require.NotNil(t, jsch, "Did not return a compiled schema") +} + +func Test_SchemaWithDefaultOptions(t *testing.T) { + valOptions := config.NewValidationOptions() + jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions) + + require.Nil(t, err, "Failed to compile Schema: %v", err) + require.NotNil(t, jsch, "Did not return a compiled schema") +} + +func Test_SchemaWithOptions(t *testing.T) { + valOptions := config.NewValidationOptions(config.WithFormatAssertions(), config.WithContentAssertions()) + + jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions) + + require.Nil(t, err, "Failed to compile Schema: %v", err) + require.NotNil(t, jsch, "Did not return a compiled schema") +} + +func Test_ObjectSchema(t *testing.T) { + valOptions := config.NewValidationOptions() + jsch, err := NewCompiledSchema("test", []byte(objectSchema), valOptions) + + require.Nil(t, err, "Failed to compile Schema: %v", err) + require.NotNil(t, jsch, "Did not return a compiled schema") +} From 6fc5851c7a1ffed8d473df5fee56d5bb14681481 Mon Sep 17 00:00:00 2001 From: Jem Day Date: Mon, 6 Jan 2025 10:12:57 -0800 Subject: [PATCH 3/8] - Address Linting issues. --- config/config.go | 5 ++++- helpers/schema_compiler.go | 29 +++++++++++++++++++++----- helpers/schema_compiler_test.go | 3 ++- parameters/cookie_parameters.go | 3 ++- parameters/header_parameters.go | 2 +- parameters/path_parameters.go | 2 +- parameters/query_parameters.go | 6 +++--- parameters/query_parameters_test.go | 4 ++-- parameters/validate_parameter.go | 15 ++++++------- parameters/validation_functions.go | 5 +++-- requests/validate_request.go | 11 +++------- responses/validate_response.go | 9 +------- schema_validation/validate_document.go | 12 +++-------- schema_validation/validate_schema.go | 9 ++------ 14 files changed, 59 insertions(+), 56 deletions(-) diff --git a/config/config.go b/config/config.go index f91e5a7..20a1b9b 100644 --- a/config/config.go +++ b/config/config.go @@ -24,7 +24,10 @@ func NewValidationOptions(opts ...Option) *ValidationOptions { // Apply any supplied overrides for _, opt := range opts { - opt(o) + // Sanity + if opt != nil { + opt(o) + } } // Done diff --git a/helpers/schema_compiler.go b/helpers/schema_compiler.go index 188a743..1a831b1 100644 --- a/helpers/schema_compiler.go +++ b/helpers/schema_compiler.go @@ -11,6 +11,11 @@ import ( // ConfigureCompiler configures a JSON Schema compiler with the desired behavior. func ConfigureCompiler(c *jsonschema.Compiler, o *config.ValidationOptions) { + // Sanity + if o == nil { + return + } + // nil is the default so this is OK. c.UseRegexpEngine(o.RegexEngine) @@ -38,18 +43,32 @@ func NewCompilerWithOptions(o *config.ValidationOptions) *jsonschema.Compiler { } // 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 { +func NewCompiledSchema(name string, jsonSchema []byte, o *config.ValidationOptions) (*jsonschema.Schema, error) { + // Fake-Up a resource name for the schema + resourceName := fmt.Sprintf("%s.json", name) + // 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) + decodedSchema, err := jsonschema.UnmarshalJSON(bytes.NewReader(jsonSchema)) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal JSON schema: %w", err) + } + + // Give our schema to the compiler. + err = compiler.AddResource(resourceName, decodedSchema) + if err != nil { + return nil, fmt.Errorf("failed to add resource to schema compiler: %w", err) + } // Try to compile it. - jsch, _ := compiler.Compile(fmt.Sprintf("%s.json", name)) + jsch, err := compiler.Compile(resourceName) + if err != nil { + return nil, fmt.Errorf("failed to compile JSON schema: %w", err) + } // Done. - return jsch + return jsch, nil } diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go index b20675c..fbf239e 100644 --- a/helpers/schema_compiler_test.go +++ b/helpers/schema_compiler_test.go @@ -3,8 +3,9 @@ package helpers import ( "testing" - "github.com/pb33f/libopenapi-validator/config" "github.com/stretchr/testify/require" + + "github.com/pb33f/libopenapi-validator/config" ) // A few simple JSON Schemas diff --git a/parameters/cookie_parameters.go b/parameters/cookie_parameters.go index c068fcf..3c3f180 100644 --- a/parameters/cookie_parameters.go +++ b/parameters/cookie_parameters.go @@ -93,7 +93,8 @@ func (v *paramValidator) ValidateCookieParamsWithPathItem(request *http.Request, "The cookie parameter", p.Name, helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) + helpers.ParameterValidationQuery, + v.options)...) } } case helpers.Array: diff --git a/parameters/header_parameters.go b/parameters/header_parameters.go index 2fc4184..4fef7a7 100644 --- a/parameters/header_parameters.go +++ b/parameters/header_parameters.go @@ -115,7 +115,7 @@ func (v *paramValidator) ValidateHeaderParamsWithPathItem(request *http.Request, "The header parameter", p.Name, helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) + helpers.ParameterValidationQuery, v.options)...) } case helpers.Array: diff --git a/parameters/path_parameters.go b/parameters/path_parameters.go index f424ca5..a0301ca 100644 --- a/parameters/path_parameters.go +++ b/parameters/path_parameters.go @@ -223,7 +223,7 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p "The path parameter", p.Name, helpers.ParameterValidation, - helpers.ParameterValidationPath)...) + helpers.ParameterValidationPath, v.options)...) } case helpers.Array: diff --git a/parameters/query_parameters.go b/parameters/query_parameters.go index f04d56f..694059d 100644 --- a/parameters/query_parameters.go +++ b/parameters/query_parameters.go @@ -173,7 +173,7 @@ doneLooking: "The query parameter", params[p].Name, helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) + helpers.ParameterValidationQuery, v.options)...) if len(validationErrors) > numErrors { // we've already added an error for this, so we can skip the rest of the values break skipValues @@ -185,7 +185,7 @@ doneLooking: // only check if items is a schema, not a boolean if sch.Items != nil && sch.Items.IsA() { validationErrors = append(validationErrors, - ValidateQueryArray(sch, params[p], ef, contentWrapped)...) + ValidateQueryArray(sch, params[p], ef, contentWrapped, v.options)...) } } } @@ -209,7 +209,7 @@ doneLooking: "The query parameter (which is an array)", params[p].Name, helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) + helpers.ParameterValidationQuery, v.options)...) break doneLooking } } diff --git a/parameters/query_parameters_test.go b/parameters/query_parameters_test.go index 31d29fe..23e6aa9 100644 --- a/parameters/query_parameters_test.go +++ b/parameters/query_parameters_test.go @@ -2633,7 +2633,7 @@ paths: } errs := ValidateParameterSchema(s, rawObject, "cake", "burger", "lemons", - "pizza", "rice", "herbs") + "pizza", "rice", "herbs", nil) assert.Len(t, errs, 1) assert.Equal(t, "lemons 'pizza' is defined as an object, "+ @@ -2877,7 +2877,7 @@ paths: } errs := ValidateParameterSchema(s, rawObject, "cake", "burger", "lemons", - "pizza", "rice", "herbs") + "pizza", "rice", "herbs", nil) assert.Len(t, errs, 1) assert.Equal(t, "lemons 'pizza' is defined as an object, "+ diff --git a/parameters/validate_parameter.go b/parameters/validate_parameter.go index ef5ceef..c2a5514 100644 --- a/parameters/validate_parameter.go +++ b/parameters/validate_parameter.go @@ -8,7 +8,6 @@ import ( "fmt" "net/url" "reflect" - "strings" "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/utils" @@ -33,8 +32,13 @@ func ValidateSingleParameterSchema( subValType string, o *config.ValidationOptions, ) (validationErrors []*errors.ValidationError) { - jsch := helpers.NewCompiledSchema(name, buildJsonRender(schema), o) + // Attempt to compile the JSON Schema + jsch, err := helpers.NewCompiledSchema(name, buildJsonRender(schema), o) + if err != nil { + return + } + // Validate the object and report any errors. scErrs := jsch.Validate(rawObject) var werras *jsonschema.ValidationError if stdError.As(scErrs, &werras) { @@ -70,6 +74,7 @@ func ValidateParameterSchema( name, validationType, subValType string, + validationOptions *config.ValidationOptions, ) []*errors.ValidationError { var validationErrors []*errors.ValidationError @@ -100,11 +105,7 @@ func ValidateParameterSchema( validEncoding = true } // 3. create a new json schema compiler and add the schema to it - compiler := jsonschema.NewCompiler() - - decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(jsonSchema))) - _ = compiler.AddResource(fmt.Sprintf("%s.json", name), decodedSchema) - jsch, _ := compiler.Compile(fmt.Sprintf("%s.json", name)) + jsch, _ := helpers.NewCompiledSchema(name, jsonSchema, validationOptions) // 4. validate the object against the schema var scErrs error diff --git a/parameters/validation_functions.go b/parameters/validation_functions.go index 1a3e273..5c63e77 100644 --- a/parameters/validation_functions.go +++ b/parameters/validation_functions.go @@ -12,6 +12,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/v3" + "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/errors" "github.com/pb33f/libopenapi-validator/helpers" ) @@ -98,7 +99,7 @@ func ValidateHeaderArray( // ValidateQueryArray will validate a query parameter that is an array func ValidateQueryArray( - sch *base.Schema, param *v3.Parameter, ef string, contentWrapped bool, + sch *base.Schema, param *v3.Parameter, ef string, contentWrapped bool, validationOptions *config.ValidationOptions, ) []*errors.ValidationError { var validationErrors []*errors.ValidationError itemsSchema := sch.Items.A.Schema() @@ -174,7 +175,7 @@ func ValidateQueryArray( "The query parameter (which is an array)", param.Name, helpers.ParameterValidation, - helpers.ParameterValidationQuery)...) + helpers.ParameterValidationQuery, validationOptions)...) case helpers.String: diff --git a/requests/validate_request.go b/requests/validate_request.go index 1e0eace..b283643 100644 --- a/requests/validate_request.go +++ b/requests/validate_request.go @@ -12,7 +12,6 @@ import ( "reflect" "regexp" "strconv" - "strings" "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/santhosh-tekuri/jsonschema/v6" @@ -37,7 +36,7 @@ func ValidateRequestSchema( jsonSchema []byte, opts ...config.Option, ) (bool, []*errors.ValidationError) { - options := config.NewValidationOptions(opts...) + validationOptions := config.NewValidationOptions(opts...) var validationErrors []*errors.ValidationError @@ -110,12 +109,8 @@ func ValidateRequestSchema( return false, validationErrors } - compiler := jsonschema.NewCompiler() - compiler.UseRegexpEngine(options.RegexEngine) // Ensure any configured regex engine is used. - compiler.UseLoader(helpers.NewCompilerLoader()) - decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(jsonSchema))) - _ = compiler.AddResource("requestBody.json", decodedSchema) - jsch, err := compiler.Compile("requestBody.json") + // Attempt to compile the JSON schema + jsch, err := helpers.NewCompiledSchema("requestBody", jsonSchema, validationOptions) if err != nil { validationErrors = append(validationErrors, &errors.ValidationError{ ValidationType: helpers.RequestBodyValidation, diff --git a/responses/validate_response.go b/responses/validate_response.go index a93fe7b..2c0a15c 100644 --- a/responses/validate_response.go +++ b/responses/validate_response.go @@ -12,7 +12,6 @@ import ( "reflect" "regexp" "strconv" - "strings" "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/santhosh-tekuri/jsonschema/v6" @@ -129,13 +128,7 @@ func ValidateResponseSchema( } // create a new jsonschema compiler and add in the rendered JSON schema. - compiler := jsonschema.NewCompiler() - compiler.UseRegexpEngine(options.RegexEngine) - compiler.UseLoader(helpers.NewCompilerLoader()) - fName := fmt.Sprintf("%s.json", helpers.ResponseBodyValidation) - decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(jsonSchema))) - _ = compiler.AddResource(fName, decodedSchema) - jsch, _ := compiler.Compile(fName) + jsch, _ := helpers.NewCompiledSchema(helpers.ResponseBodyValidation, jsonSchema, options) // validate the object against the schema scErrs := jsch.Validate(decodedObj) diff --git a/schema_validation/validate_document.go b/schema_validation/validate_document.go index 4a72afd..1c430ad 100644 --- a/schema_validation/validate_document.go +++ b/schema_validation/validate_document.go @@ -6,7 +6,6 @@ package schema_validation import ( "errors" "fmt" - "strings" "github.com/pb33f/libopenapi" "github.com/santhosh-tekuri/jsonschema/v6" @@ -29,15 +28,10 @@ func ValidateOpenAPIDocument(doc libopenapi.Document, opts ...config.Option) (bo var validationErrors []*liberrors.ValidationError decodedDocument := *info.SpecJSON - compiler := jsonschema.NewCompiler() - compiler.UseRegexpEngine(options.RegexEngine) - compiler.UseLoader(helpers.NewCompilerLoader()) - - decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(loadedSchema))) - - _ = compiler.AddResource("schema.json", decodedSchema) - jsch, _ := compiler.Compile("schema.json") + // Compile the JSON Schema + jsch, _ := helpers.NewCompiledSchema("schema", []byte(loadedSchema), options) + // Validate the document scErrs := jsch.Validate(decodedDocument) var schemaValidationErrors []*liberrors.SchemaValidationFailure diff --git a/schema_validation/validate_schema.go b/schema_validation/validate_schema.go index 6dec2a6..8691bf2 100644 --- a/schema_validation/validate_schema.go +++ b/schema_validation/validate_schema.go @@ -12,7 +12,6 @@ import ( "reflect" "regexp" "strconv" - "strings" "sync" "github.com/pb33f/libopenapi/datamodel/high/base" @@ -128,13 +127,9 @@ func (s *schemaValidator) validateSchema(schema *base.Schema, payload []byte, de } } - compiler := jsonschema.NewCompiler() - compiler.UseRegexpEngine(s.options.RegexEngine) - compiler.UseLoader(helpers.NewCompilerLoader()) - decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(jsonSchema))) - _ = compiler.AddResource("schema.json", decodedSchema) - jsch, err := compiler.Compile("schema.json") + // Build the compiled JSON Schema + jsch, err := helpers.NewCompiledSchema("schema", jsonSchema, s.options) var schemaValidationErrors []*liberrors.SchemaValidationFailure From 2484c6a016615f25e890a1f927ec6b7f9f611630 Mon Sep 17 00:00:00 2001 From: Jem Day Date: Mon, 6 Jan 2025 11:05:49 -0800 Subject: [PATCH 4/8] - Increase test coverage. --- helpers/schema_compiler_test.go | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go index fbf239e..77cedca 100644 --- a/helpers/schema_compiler_test.go +++ b/helpers/schema_compiler_test.go @@ -3,6 +3,7 @@ package helpers import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/pb33f/libopenapi-validator/config" @@ -61,3 +62,52 @@ func Test_ObjectSchema(t *testing.T) { require.Nil(t, err, "Failed to compile Schema: %v", err) require.NotNil(t, jsch, "Did not return a compiled schema") } + +func Test_ValidJSONSchemaWithInvalidContent(t *testing.T) { + // An example of a dubious JSON Schema + const badSchema = `{ + "type": "you-dont-know-me", + "format": "date", + "minLength": 10 +}` + + jsch, err := NewCompiledSchema("test", []byte(badSchema), nil) + + assert.NotNil(t, err, "Expected an error to be thrown") + assert.Nil(t, jsch, "invalid schema compiled!") +} + +func Test_MalformedSONSchema(t *testing.T) { + // An example of a JSON schema with malformed JSON + const badSchema = `{ + "type": "you-dont-know-me", + "format": "date" + "minLength": 10 +}` + + jsch, err := NewCompiledSchema("test", []byte(badSchema), nil) + + assert.NotNil(t, err, "Expected an error to be thrown") + assert.Nil(t, jsch, "invalid schema compiled!") +} + +func Test_ValidJSONSchemaWithIncompleteContent(t *testing.T) { + // An example of a dJSON schema with references to non-existent stuff + const incompleteSchema = `{ + "type": "object", + "title" : "unresolvable", + "properties" : { + "name" : { + "type": "string", + }, + "species" : { + "$ref": "#/$defs/speciesEnum" + } + } +}` + + jsch, err := NewCompiledSchema("test", []byte(incompleteSchema), nil) + + assert.NotNil(t, err, "Expected an error to be thrown") + assert.Nil(t, jsch, "invalid schema compiled!") +} From 8e7e11ae656dd7937b9da9af80011561b9bceb19 Mon Sep 17 00:00:00 2001 From: Jem Day Date: Tue, 7 Jan 2025 10:53:10 -0800 Subject: [PATCH 5/8] - Address review comments --- helpers/schema_compiler_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go index 77cedca..530de77 100644 --- a/helpers/schema_compiler_test.go +++ b/helpers/schema_compiler_test.go @@ -34,7 +34,7 @@ const objectSchema = `{ func Test_SchemaWithNilOptions(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(stringSchema), nil) - require.Nil(t, err, "Failed to compile Schema: %v", err) + require.Nil(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } @@ -42,7 +42,7 @@ func Test_SchemaWithDefaultOptions(t *testing.T) { valOptions := config.NewValidationOptions() jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions) - require.Nil(t, err, "Failed to compile Schema: %v", err) + require.Nil(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } @@ -51,7 +51,7 @@ func Test_SchemaWithOptions(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions) - require.Nil(t, err, "Failed to compile Schema: %v", err) + require.Nil(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } @@ -59,7 +59,7 @@ func Test_ObjectSchema(t *testing.T) { valOptions := config.NewValidationOptions() jsch, err := NewCompiledSchema("test", []byte(objectSchema), valOptions) - require.Nil(t, err, "Failed to compile Schema: %v", err) + require.Nil(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } From a4107b94e187199f54b45ded3f4fc6180ae63893 Mon Sep 17 00:00:00 2001 From: Jem Day Date: Wed, 8 Jan 2025 09:24:03 -0800 Subject: [PATCH 6/8] - Address review comments --- helpers/schema_compiler_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go index 530de77..bdbb370 100644 --- a/helpers/schema_compiler_test.go +++ b/helpers/schema_compiler_test.go @@ -34,7 +34,7 @@ const objectSchema = `{ func Test_SchemaWithNilOptions(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(stringSchema), nil) - require.Nil(t, err, "Failed to compile Schema") + require.NoError(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } @@ -42,7 +42,7 @@ func Test_SchemaWithDefaultOptions(t *testing.T) { valOptions := config.NewValidationOptions() jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions) - require.Nil(t, err, "Failed to compile Schema") + require.NoError(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } @@ -51,7 +51,7 @@ func Test_SchemaWithOptions(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(stringSchema), valOptions) - require.Nil(t, err, "Failed to compile Schema") + require.NoError(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } @@ -59,7 +59,7 @@ func Test_ObjectSchema(t *testing.T) { valOptions := config.NewValidationOptions() jsch, err := NewCompiledSchema("test", []byte(objectSchema), valOptions) - require.Nil(t, err, "Failed to compile Schema") + require.NoError(t, err, "Failed to compile Schema") require.NotNil(t, jsch, "Did not return a compiled schema") } From a9f8881ba0190a53982d5733ae57ecf1ca51237d Mon Sep 17 00:00:00 2001 From: Jem Day Date: Fri, 10 Jan 2025 12:53:07 -0800 Subject: [PATCH 7/8] - Address review comments --- config/config.go | 1 - helpers/schema_compiler.go | 2 +- helpers/schema_compiler_test.go | 6 +++--- parameters/query_parameters_test.go | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/config/config.go b/config/config.go index 20a1b9b..c41a8ee 100644 --- a/config/config.go +++ b/config/config.go @@ -24,7 +24,6 @@ func NewValidationOptions(opts ...Option) *ValidationOptions { // Apply any supplied overrides for _, opt := range opts { - // Sanity if opt != nil { opt(o) } diff --git a/helpers/schema_compiler.go b/helpers/schema_compiler.go index 1a831b1..27310b4 100644 --- a/helpers/schema_compiler.go +++ b/helpers/schema_compiler.go @@ -11,8 +11,8 @@ import ( // ConfigureCompiler configures a JSON Schema compiler with the desired behavior. func ConfigureCompiler(c *jsonschema.Compiler, o *config.ValidationOptions) { - // Sanity if o == nil { + // Sanity return } diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go index bdbb370..cb9139a 100644 --- a/helpers/schema_compiler_test.go +++ b/helpers/schema_compiler_test.go @@ -73,7 +73,7 @@ func Test_ValidJSONSchemaWithInvalidContent(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(badSchema), nil) - assert.NotNil(t, err, "Expected an error to be thrown") + assert.NoError(t, err, "Expected an error to be thrown") assert.Nil(t, jsch, "invalid schema compiled!") } @@ -87,7 +87,7 @@ func Test_MalformedSONSchema(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(badSchema), nil) - assert.NotNil(t, err, "Expected an error to be thrown") + assert.NoError(t, err, "Expected an error to be thrown") assert.Nil(t, jsch, "invalid schema compiled!") } @@ -108,6 +108,6 @@ func Test_ValidJSONSchemaWithIncompleteContent(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(incompleteSchema), nil) - assert.NotNil(t, err, "Expected an error to be thrown") + assert.NoError(t, err, "Expected an error to be thrown") assert.Nil(t, jsch, "invalid schema compiled!") } diff --git a/parameters/query_parameters_test.go b/parameters/query_parameters_test.go index 23e6aa9..e1398e3 100644 --- a/parameters/query_parameters_test.go +++ b/parameters/query_parameters_test.go @@ -693,7 +693,7 @@ paths: valid, errors := v.ValidateQueryParams(request) assert.True(t, valid) - assert.Len(t, errors, 0) + assert.Empty(t, errors) } func TestNewValidator_QueryParamInvalidDateFormat(t *testing.T) { @@ -745,7 +745,7 @@ paths: valid, errors := v.ValidateQueryParams(request) assert.True(t, valid) - assert.Len(t, errors, 0) + assert.Empty(t, errors) } func TestNewValidator_QueryParamInvalidDateTimeFormat(t *testing.T) { From 4b65d024e9b28af45e3f26cff51534c2395f881d Mon Sep 17 00:00:00 2001 From: Jem Day Date: Fri, 10 Jan 2025 13:02:39 -0800 Subject: [PATCH 8/8] - Fixed corrupted tests (more haste, less speed) --- helpers/schema_compiler_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go index cb9139a..a457c89 100644 --- a/helpers/schema_compiler_test.go +++ b/helpers/schema_compiler_test.go @@ -73,7 +73,7 @@ func Test_ValidJSONSchemaWithInvalidContent(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(badSchema), nil) - assert.NoError(t, err, "Expected an error to be thrown") + assert.Error(t, err, "Expected an error to be thrown") assert.Nil(t, jsch, "invalid schema compiled!") } @@ -87,7 +87,7 @@ func Test_MalformedSONSchema(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(badSchema), nil) - assert.NoError(t, err, "Expected an error to be thrown") + assert.Error(t, err, "Expected an error to be thrown") assert.Nil(t, jsch, "invalid schema compiled!") } @@ -108,6 +108,6 @@ func Test_ValidJSONSchemaWithIncompleteContent(t *testing.T) { jsch, err := NewCompiledSchema("test", []byte(incompleteSchema), nil) - assert.NoError(t, err, "Expected an error to be thrown") + assert.Error(t, err, "Expected an error to be thrown") assert.Nil(t, jsch, "invalid schema compiled!") }