From f96021d4b27020476d771aea571daeb6ff7709a8 Mon Sep 17 00:00:00 2001 From: Jem Day Date: Wed, 11 Dec 2024 11:19:57 -0800 Subject: [PATCH] Addressed Review Comments - Removed .gitignore - Fixed import ordering - Adopted 'Options pattern' for configuration. --- .gitignore | 1 - parameters/parameters.go | 25 +++++++++++++--------- requests/request_body.go | 29 ++++++++++++++++--------- requests/validate_body.go | 2 +- requests/validate_request.go | 10 ++++----- responses/response_body.go | 25 +++++++++++++++------- responses/validate_body.go | 2 +- responses/validate_response.go | 12 +++++------ validator.go | 39 ++++++++++++++++------------------ validator_test.go | 20 +++++++++++++---- 10 files changed, 98 insertions(+), 67 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9f11b75..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.idea/ diff --git a/parameters/parameters.go b/parameters/parameters.go index 17b8784..4cd4d0f 100644 --- a/parameters/parameters.go +++ b/parameters/parameters.go @@ -4,9 +4,10 @@ package parameters import ( - "github.com/santhosh-tekuri/jsonschema/v6" "net/http" + "github.com/santhosh-tekuri/jsonschema/v6" + v3 "github.com/pb33f/libopenapi/datamodel/high/v3" "github.com/pb33f/libopenapi-validator/errors" @@ -66,23 +67,27 @@ type ParameterValidator interface { ValidateSecurityWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) } -type Config struct { - RegexEngine jsonschema.RegexpEngine +type Option func(validator *paramValidator) + +func WithRegexEngine(engine jsonschema.RegexpEngine) Option { + return func(pv *paramValidator) { + pv.regexEngine = engine + } } // NewParameterValidator will create a new ParameterValidator from an OpenAPI 3+ document -func NewParameterValidator(document *v3.Document, cfg ...Config) ParameterValidator { +func NewParameterValidator(document *v3.Document, opts ...Option) ParameterValidator { - config := Config{} + pv := paramValidator{document: document} - if len(cfg) > 0 { - config = cfg[0] + for _, opt := range opts { + opt(&pv) } - return ¶mValidator{config, document} + return &pv } type paramValidator struct { - Config - document *v3.Document + regexEngine jsonschema.RegexpEngine + document *v3.Document } diff --git a/requests/request_body.go b/requests/request_body.go index 589d2ef..1691472 100644 --- a/requests/request_body.go +++ b/requests/request_body.go @@ -4,10 +4,11 @@ package requests import ( - "github.com/santhosh-tekuri/jsonschema/v6" "net/http" "sync" + "github.com/santhosh-tekuri/jsonschema/v6" + "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/v3" @@ -30,20 +31,28 @@ type RequestBodyValidator interface { ValidateRequestBodyWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) } -type Config struct { - RegexEngine jsonschema.RegexpEngine +type configOptions struct { + regexEngine jsonschema.RegexpEngine } -// NewRequestBodyValidator will create a new RequestBodyValidator from an OpenAPI 3+ document -func NewRequestBodyValidator(document *v3.Document, cfg ...Config) RequestBodyValidator { +type Option func(options *configOptions) - config := Config{} // Default +func WithRegexEngine(engine jsonschema.RegexpEngine) Option { + return func(rbv *configOptions) { + rbv.regexEngine = engine + } +} + +// NewRequestBodyValidator will create a new RequestBodyValidator from an OpenAPI 3+ document +func NewRequestBodyValidator(document *v3.Document, opt ...Option) RequestBodyValidator { - if len(cfg) > 0 { - config = cfg[0] + cfg := configOptions{} // Default Options + for _, o := range opt { + o(&cfg) } - return &requestBodyValidator{Config: config, document: document, schemaCache: &sync.Map{}} + return &requestBodyValidator{configOptions: cfg, document: document, schemaCache: &sync.Map{}} + } type schemaCache struct { @@ -53,7 +62,7 @@ type schemaCache struct { } type requestBodyValidator struct { - Config + configOptions document *v3.Document schemaCache *sync.Map } diff --git a/requests/validate_body.go b/requests/validate_body.go index f1de59a..5c8ae91 100644 --- a/requests/validate_body.go +++ b/requests/validate_body.go @@ -109,7 +109,7 @@ func (v *requestBodyValidator) ValidateRequestBodyWithPathItem(request *http.Req } // render the schema, to be used for validation - validationSucceeded, validationErrors := ValidateRequestSchema(request, schema, renderedInline, renderedJSON, v.Config) + validationSucceeded, validationErrors := ValidateRequestSchema(request, schema, renderedInline, renderedJSON, WithRegexEngine(v.regexEngine)) errors.PopulateValidationErrors(validationErrors, request, pathValue) diff --git a/requests/validate_request.go b/requests/validate_request.go index 66676a8..e871b79 100644 --- a/requests/validate_request.go +++ b/requests/validate_request.go @@ -34,12 +34,12 @@ func ValidateRequestSchema( schema *base.Schema, renderedSchema, jsonSchema []byte, - cfg ...Config, + opts ...Option, ) (bool, []*errors.ValidationError) { - config := Config{} // Default - if len(cfg) > 0 { - config = cfg[0] + config := configOptions{} + for _, opt := range opts { + opt(&config) } var validationErrors []*errors.ValidationError @@ -114,7 +114,7 @@ func ValidateRequestSchema( } compiler := jsonschema.NewCompiler() - compiler.UseRegexpEngine(config.RegexEngine) // Ensure any configured regex engine is used. + compiler.UseRegexpEngine(config.regexEngine) // Ensure any configured regex engine is used. compiler.UseLoader(helpers.NewCompilerLoader()) decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(jsonSchema))) _ = compiler.AddResource("requestBody.json", decodedSchema) diff --git a/responses/response_body.go b/responses/response_body.go index 9ccbafd..4890efa 100644 --- a/responses/response_body.go +++ b/responses/response_body.go @@ -31,20 +31,29 @@ type ResponseBodyValidator interface { ValidateResponseBodyWithPathItem(request *http.Request, response *http.Response, pathItem *v3.PathItem, pathFound string) (bool, []*errors.ValidationError) } -type Config struct { - RegexEngine jsonschema.RegexpEngine +type Option func(validator *configOptions) + +func WithRegexEngine(engine jsonschema.RegexpEngine) Option { + return func(v *configOptions) { + v.regexEngine = engine + } +} + +type configOptions struct { + regexEngine jsonschema.RegexpEngine } // NewResponseBodyValidator will create a new ResponseBodyValidator from an OpenAPI 3+ document -func NewResponseBodyValidator(document *v3.Document, cfg ...Config) ResponseBodyValidator { +func NewResponseBodyValidator(document *v3.Document, opts ...Option) ResponseBodyValidator { - config := Config{} // Default + cfg := configOptions{} // Default Config - if len(cfg) > 0 { - config = cfg[0] + for _, opt := range opts { + opt(&cfg) } - return &responseBodyValidator{Config: config, document: document, schemaCache: &sync.Map{}} + return &responseBodyValidator{configOptions: cfg, document: document, schemaCache: &sync.Map{}} + } type schemaCache struct { @@ -54,7 +63,7 @@ type schemaCache struct { } type responseBodyValidator struct { - Config + configOptions document *v3.Document schemaCache *sync.Map } diff --git a/responses/validate_body.go b/responses/validate_body.go index f101a47..4b5392f 100644 --- a/responses/validate_body.go +++ b/responses/validate_body.go @@ -156,7 +156,7 @@ func (v *responseBodyValidator) checkResponseSchema( } // render the schema, to be used for validation - valid, vErrs := ValidateResponseSchema(request, response, schema, renderedInline, renderedJSON, v.Config) + valid, vErrs := ValidateResponseSchema(request, response, schema, renderedInline, renderedJSON, WithRegexEngine(v.regexEngine)) if !valid { validationErrors = append(validationErrors, vErrs...) } diff --git a/responses/validate_response.go b/responses/validate_response.go index 23dbcb1..93a6777 100644 --- a/responses/validate_response.go +++ b/responses/validate_response.go @@ -38,13 +38,13 @@ func ValidateResponseSchema( schema *base.Schema, renderedSchema, jsonSchema []byte, - cfg ...Config, + cfg ...Option, ) (bool, []*errors.ValidationError) { - config := Config{} // A Default - - if len(cfg) > 0 { - config = cfg[0] + // Apply config options + config := configOptions{} // Default config + for _, opt := range cfg { + opt(&config) } var validationErrors []*errors.ValidationError @@ -134,7 +134,7 @@ func ValidateResponseSchema( // create a new jsonschema compiler and add in the rendered JSON schema. compiler := jsonschema.NewCompiler() - compiler.UseRegexpEngine(config.RegexEngine) + compiler.UseRegexpEngine(config.regexEngine) compiler.UseLoader(helpers.NewCompilerLoader()) fName := fmt.Sprintf("%s.json", helpers.ResponseBodyValidation) decodedSchema, _ := jsonschema.UnmarshalJSON(strings.NewReader(string(jsonSchema))) diff --git a/validator.go b/validator.go index db3307c..8f3a515 100644 --- a/validator.go +++ b/validator.go @@ -63,48 +63,44 @@ type Validator interface { GetResponseBodyValidator() responses.ResponseBodyValidator } -// Configuration Holds any Validator configuration overrides. -type Configuration struct { - // Use this regex engine in place of the standard Go (RE2) pattern processor - RegexEngine jsonschema.RegexpEngine +type Option func(*validator) + +func WithRegexEngine(engine jsonschema.RegexpEngine) Option { + return func(v *validator) { + v.regexEngine = engine + } } // NewValidator will create a new Validator from an OpenAPI 3+ document -func NewValidator(document libopenapi.Document, config ...Configuration) (Validator, []error) { +func NewValidator(document libopenapi.Document, opts ...Option) (Validator, []error) { m, errs := document.BuildV3Model() if errs != nil { return nil, errs } - v := NewValidatorFromV3Model(&m.Model, config...) + v := NewValidatorFromV3Model(&m.Model, opts...) v.(*validator).document = document return v, nil } // NewValidatorFromV3Model will create a new Validator from an OpenAPI Model -func NewValidatorFromV3Model(m *v3.Document, config ...Configuration) Validator { +func NewValidatorFromV3Model(m *v3.Document, opts ...Option) Validator { - // Assume a default configuration - cfg := Configuration{} + v := &validator{v3Model: m} - if len(config) > 0 { - cfg = config[0] + for _, opt := range opts { + opt(v) } // create a new parameter validator - paramValidator := parameters.NewParameterValidator(m, parameters.Config{RegexEngine: cfg.RegexEngine}) + v.paramValidator = parameters.NewParameterValidator(m, parameters.WithRegexEngine(v.regexEngine)) - // create a new request body validator - reqBodyValidator := requests.NewRequestBodyValidator(m, requests.Config{RegexEngine: cfg.RegexEngine}) + // create aq new request body validator + v.requestValidator = requests.NewRequestBodyValidator(m) // create a response body validator - respBodyValidator := responses.NewResponseBodyValidator(m, responses.Config{RegexEngine: cfg.RegexEngine}) + v.responseValidator = responses.NewResponseBodyValidator(m, responses.WithRegexEngine(v.regexEngine)) - return &validator{ - v3Model: m, - requestValidator: reqBodyValidator, - responseValidator: respBodyValidator, - paramValidator: paramValidator, - } + return v } func (v *validator) GetParameterValidator() parameters.ParameterValidator { @@ -325,6 +321,7 @@ type validator struct { paramValidator parameters.ParameterValidator requestValidator requests.RequestBodyValidator responseValidator responses.ResponseBodyValidator + regexEngine jsonschema.RegexpEngine } func runValidation(control, doneChan chan struct{}, diff --git a/validator_test.go b/validator_test.go index 8bc55e4..7b5a6eb 100644 --- a/validator_test.go +++ b/validator_test.go @@ -6,6 +6,7 @@ package validator import ( "bytes" "encoding/json" + "github.com/santhosh-tekuri/jsonschema/v6" "net/http" "net/http/httptest" "os" @@ -136,13 +137,24 @@ func TestNewValidator_ValidateDocument(t *testing.T) { assert.Len(t, errs, 0) } -func TestNewValidator_WithConfig(t *testing.T) { +type alwaysMatchesRegex jsonschema.RegexpEngine - // This needs work. - validatorConfig := Configuration{} +func (dr *alwaysMatchesRegex) MatchString(s string) bool { + return true +} + +func (dr *alwaysMatchesRegex) String() string { + return "" +} + +func fakeRegexEngine(s string) (jsonschema.Regexp, error) { + return (*alwaysMatchesRegex)(nil), nil +} + +func TestNewValidator_WithRegex(t *testing.T) { doc, _ := libopenapi.NewDocument(petstoreBytes) - v, _ := NewValidator(doc, validatorConfig) + v, _ := NewValidator(doc, WithRegexEngine(fakeRegexEngine)) require.NotNil(t, v, "Failed to build validator") valid, errs := v.ValidateDocument() assert.True(t, valid)