diff --git a/go.mod b/go.mod index 340096e..7ea3206 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -require ( - github.com/stretchr/testify v1.10.0 - golang.org/x/text v0.14.0 // indirect -) +require golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 25d9a0b..2c8aa28 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,10 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/plugins/config.yaml b/plugins/config.yaml deleted file mode 100644 index bbf0ca6..0000000 --- a/plugins/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -plugins: - validation_plugin: - id: tekuriValidator - config: - schema_dir: - plugin_path: plugins/implementations/ \ No newline at end of file diff --git a/plugins/definitions/plugin.go b/plugins/definitions/plugin.go deleted file mode 100644 index 3f31bd6..0000000 --- a/plugins/definitions/plugin.go +++ /dev/null @@ -1,14 +0,0 @@ -package definitions - -import "context" - -// Validator interface for schema validation -type Validator interface { - Validate(ctx context.Context, b []byte) error -} - -// ValidatorProvider interface for creating validators -type ValidatorProvider interface { - //Get(p string) (Validator, error) - Initialize(schemaDir string) (map[string]Validator, error) -} diff --git a/plugins/implementations/coverage.html b/plugins/implementations/coverage.html deleted file mode 100644 index 92dae5f..0000000 --- a/plugins/implementations/coverage.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - - implementations: Go Coverage Report - - - -
- -
- not tracked - - not covered - covered - -
-
-
- - - -
- - - diff --git a/plugins/implementations/plugin_impl_test.go b/plugins/implementations/plugin_impl_test.go deleted file mode 100644 index 7ad610f..0000000 --- a/plugins/implementations/plugin_impl_test.go +++ /dev/null @@ -1,303 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "log" - "os" - "path/filepath" - "strings" - "testing" -) - -type Payload struct { - Context Context `json:"context"` - Message Message `json:"message"` -} - -type Context struct{} -type Message struct{} - -func TestValidDirectory(t *testing.T) { - provider := &TekuriValidatorProvider{} - schemaDir := "../testData/schema_valid/" - _, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } -} - -func TestValidPayload(t *testing.T) { - schemaDir := "../testData/schema_valid/" - if _, err := os.Stat(schemaDir); os.IsNotExist(err) { - t.Fatalf("Schema directory does not exist: %v", schemaDir) - } - provider := &TekuriValidatorProvider{} - validators, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("Failed to initialize schema provider: %v", err) - } - var validator *TekuriValidator - for _, v := range validators { - var ok bool - validator, ok = v.(*TekuriValidator) - if ok { - break - } - } - if validator == nil { - t.Fatalf("No validators found in the map") - } - - payloadFilePath := "../testData/payloads/search.json" - payloadData, err := os.ReadFile(payloadFilePath) - if err != nil { - t.Fatalf("Failed to read payload data: %v", err) - } - var payload Payload - err = json.Unmarshal(payloadData, &payload) - if err != nil { - log.Fatalf("Failed to unmarshal payload data: %v", err) - } - err = validator.Validate(context.Background(), payloadData) - if err != nil { - t.Errorf("Validation failed: %v", err) - } else { - fmt.Println("Validation succeeded.") - } -} - -func TestInValidDirectory(t *testing.T) { - provider := &TekuriValidatorProvider{} - schemaDir := "../testData/schema/ondc_trv10/" - _, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("failed to read schema directory: %v", err) - } -} - -func TestInvalidCompileFile(t *testing.T) { - schemaDir := "../testData/invalid_compile_schema/" - // if _, err := os.Stat(schemaDir); os.IsNotExist(err) { - // t.Fatalf("Schema directory does not exist: %v", schemaDir) - // } - provider := &TekuriValidatorProvider{} - _, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("failed to compile JSON schema : %v", err) - } -} - -func TestInvalidCompileSchema(t *testing.T) { - schemaDir := "../testData/invalid_schemas/" - if _, err := os.Stat(schemaDir); os.IsNotExist(err) { - t.Fatalf("Schema directory does not exist: %v", schemaDir) - } - provider := &TekuriValidatorProvider{} - compiledSchema, _ := provider.Initialize(schemaDir) - fmt.Println(compiledSchema) - if compiledSchema == nil { - t.Fatalf("compiled schema is nil : ") - - } -} - -func TestPayloadWithExtraFields(t *testing.T) { - schemaDir := "../testData/schema_valid/" - if _, err := os.Stat(schemaDir); os.IsNotExist(err) { - t.Fatalf("Schema directory does not exist: %v", schemaDir) - } - provider := &TekuriValidatorProvider{} - validators, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("Failed to initialize schema provider: %v", err) - } - var validator *TekuriValidator - for _, v := range validators { - var ok bool - validator, ok = v.(*TekuriValidator) - if ok { - break - } - } - if validator == nil { - t.Fatalf("No validators found in the map") - } - payloadFilePath := "../testData/payloads/search_extraField.json" - payloadData, err := os.ReadFile(payloadFilePath) - if err != nil { - t.Fatalf("Failed to read payload data: %v", err) - } - var payload Payload - err = json.Unmarshal(payloadData, &payload) - if err != nil { - log.Fatalf("Failed to unmarshal payload data: %v", err) - } - err = validator.Validate(context.Background(), payloadData) - if err != nil { - t.Errorf("Validation failed due to extra fields: %v", err) - } else { - fmt.Println("Validation succeeded.") - } - -} - -func TestPayloadWithMissingFields(t *testing.T) { - schemaDir := "../testData/schema_valid/" - if _, err := os.Stat(schemaDir); os.IsNotExist(err) { - t.Fatalf("Schema directory does not exist: %v", schemaDir) - } - provider := &TekuriValidatorProvider{} - validators, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("Failed to initialize schema provider: %v", err) - } - var validator *TekuriValidator - for _, v := range validators { - var ok bool - validator, ok = v.(*TekuriValidator) - if ok { - break - } - } - if validator == nil { - t.Fatalf("No validators found in the map") - } - payloadFilePath := "../testData/payloads/search_missingField.json" - payloadData, err := os.ReadFile(payloadFilePath) - if err != nil { - t.Fatalf("Failed to read payload data: %v", err) - } - var payload Payload - err = json.Unmarshal(payloadData, &payload) - if err != nil { - log.Fatalf("Failed to unmarshal payload data: %v", err) - } - err = validator.Validate(context.Background(), payloadData) - if err != nil { - t.Errorf("Validation failed with missing fields: %v", err) - } else { - fmt.Println("Validation succeeded.") - } - -} - -func TestInValidPayload(t *testing.T) { - schemaDir := "../testData/schema_valid/" - - if _, err := os.Stat(schemaDir); os.IsNotExist(err) { - t.Fatalf("Schema directory does not exist: %v", schemaDir) - } - provider := &TekuriValidatorProvider{} - validators, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("Failed to initialize schema provider: %v", err) - } - var validator *TekuriValidator - for _, v := range validators { - var ok bool - validator, ok = v.(*TekuriValidator) - if ok { - break - } - } - if validator == nil { - t.Fatalf("No validators found in the map") - } - invalidPayloadData := []byte(`"invalid": "data"}`) - err = validator.Validate(context.Background(), invalidPayloadData) - if err != nil { - t.Fatalf("Validation failed: %v", err) - } -} - -func TestInValidateUnmarshalData(t *testing.T) { - schemaDir := "../testdata/schema_valid/" - - if _, err := os.Stat(schemaDir); os.IsNotExist(err) { - t.Fatalf("Schema directory does not exist: %v", schemaDir) - } - - provider := &TekuriValidatorProvider{} - validators, err := provider.Initialize(schemaDir) - if err != nil { - t.Fatalf("Failed to initialize schema provider: %v", err) - } - var validator *TekuriValidator - for _, v := range validators { - var ok bool - validator, ok = v.(*TekuriValidator) - if ok { - break - } - } - if validator == nil { - t.Fatalf("No validators found in the map") - } - invalidPayloadData := []byte(`{"invalid": "data`) - err = validator.Validate(context.Background(), invalidPayloadData) - if err != nil { - t.Errorf("Error while unmarshaling the data: %v", err) - } -} - -func TestGetProvider(t *testing.T) { - expected := providerInstance - actual := GetProvider() - - if actual != expected { - t.Fatalf("expected %v, got %v", expected, actual) - } else { - t.Logf("GetProvider returned the expected providerInstance") - } -} - -func TestInitialize_SchemaPathNotDirectory(t *testing.T) { - vp := &TekuriValidatorProvider{} - filePath := "../testdata/directory.json" - - _, err := vp.Initialize(filePath) - - if err == nil || !strings.Contains(err.Error(), "provided schema path is not a directory") { - t.Errorf("Expected error about non-directory schema path, got: %v", err) - } -} - -func TestInitialize_InvalidSchemaFileStructure(t *testing.T) { - schemaDir := "../testData/invalid_structure" - provider := &TekuriValidatorProvider{} - - _, err := provider.Initialize(schemaDir) - if err == nil || !strings.Contains(err.Error(), "invalid schema file structure") { - t.Fatalf("Expected error for invalid schema file structure, got: %v", err) - } - -} - -func TestInitialize_FailedToGetRelativePath(t *testing.T) { - schemaDir := "../testData/valid_schemas" - provider := &TekuriValidatorProvider{} - - // Use an absolute path for schemaDir and a relative path for the file to simulate the error - absSchemaDir, err := filepath.Abs(schemaDir) - if err != nil { - t.Fatalf("Failed to get absolute path for schema directory: %v", err) - } - - // Temporarily change the working directory to simulate different volumes - originalWd, err := os.Getwd() - if err != nil { - t.Fatalf("Failed to get current working directory: %v", err) - } - defer os.Chdir(originalWd) // Restore the original working directory after the test - - // Change to a different directory - os.Chdir("/tmp") - - _, err = provider.Initialize(absSchemaDir) - if err == nil || !strings.Contains(err.Error(), "failed to get relative path for file") { - t.Fatalf("Expected error for failing to get relative path, got: %v", err) - } - -} diff --git a/plugins/manager.go b/plugins/manager.go deleted file mode 100644 index f99f0f3..0000000 --- a/plugins/manager.go +++ /dev/null @@ -1,115 +0,0 @@ -package plugins - -import ( - //"beckn-onix/plugins/plugin_definition" - "beckn-onix/plugins/definitions" - "fmt" - "log" - "os" - "path/filepath" - "plugin" - "time" - - "gopkg.in/yaml.v2" -) - -// PluginConfig represents the configuration for plugins, including the plugins themselves. -type PluginConfig struct { - Plugins Plugins `yaml:"plugins"` -} - -// Plugins holds the various plugin types used in the configuration. -type Plugins struct { - ValidationPlugin ValidationPlugin `yaml:"validation_plugin"` -} - -// ValidationPlugin represents a plugin with an ID, configuration, and the path to the plugin. -type ValidationPlugin struct { - ID string `yaml:"id"` - Config PluginDetails `yaml:"config"` - PluginPath string `yaml:"plugin_path"` -} - -// PluginDetails contains information about the plugin schema directory. -type PluginDetails struct { - Schema string `yaml:"schema_dir"` -} - -// PluginManager manages the loading and execution of plugins. -type PluginManager struct { - validatorProvider definitions.ValidatorProvider -} - -// NewValidatorProvider initializes the PluginManager with the given configuration. -func NewValidatorProvider(pluginsConfig PluginConfig, debug bool) (*PluginManager, map[string]definitions.Validator, error) { - start := time.Now() - // var memStatsBefore runtime.MemStats - // Only capture metrics in debug mode - // if debug { - // start = time.Now() - // runtime.ReadMemStats(&memStatsBefore) - // } - validationPlugin := pluginsConfig.Plugins.ValidationPlugin - if validationPlugin.ID == "" { - return nil, nil, fmt.Errorf("validation_plugin ID is empty") - } - - pluginPath := filepath.Join(validationPlugin.PluginPath, validationPlugin.ID+".so") - - // Check if the plugin path is empty - if pluginPath == "" { - return nil, nil, fmt.Errorf("plugin path is empty") - } - - // Load the plugin - p, err := plugin.Open(pluginPath) - if err != nil { - return nil, nil, fmt.Errorf("failed to open plugin: %v", err) - } - - vpSymbol, err := p.Lookup("GetProvider") - if err != nil { - return nil, nil, err - } - getProviderFunc, ok := vpSymbol.(func() definitions.ValidatorProvider) - if !ok { - return nil, nil, fmt.Errorf("failed to cast to *plugins.ValidatorProvider") - } - validatorProvider := getProviderFunc() - - schemaDir := pluginsConfig.Plugins.ValidationPlugin.Config.Schema - validator, err := validatorProvider.Initialize(schemaDir) - if err != nil { - log.Fatalf("Failed to initialize validator provider: %v", err) - } - - // Log metrics if in debug mode - // if debug { - // var memStatsAfter runtime.MemStats - // runtime.ReadMemStats(&memStatsAfter) - // fmt.Printf("Memory allocated during plugin boot-up: %v MiB\n", (memStatsAfter.Alloc-memStatsBefore.Alloc)/1024/1024) - // fmt.Printf("Plugin boot-up executed in %s\n", time.Since(start)) - // } - - fmt.Printf("plugin boot-up executed in %s\n", time.Since(start)) - return &PluginManager{validatorProvider: validatorProvider}, validator, nil -} - -// LoadPluginsConfig loads the plugins configuration from a YAML file. -func LoadPluginsConfig(filePath string) (PluginConfig, error) { - // start := time.Now() - - data, err := os.ReadFile(filePath) - if err != nil { - return PluginConfig{}, err - } - - var config PluginConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return PluginConfig{}, err - } - // fmt.Printf("loadconfig executed in %s\n", time.Since(start)) - - return config, nil -} diff --git a/plugins/testData/invalid_schemas/ondc_trv10/init.json b/plugins/testData/invalid_schemas/ondc_trv10/init.json deleted file mode 100644 index e69de29..0000000 diff --git a/plugins/testData/invalid_structure/v2.0.0/schema.json b/plugins/testData/invalid_structure/v2.0.0/schema.json deleted file mode 100644 index 3b7396d..0000000 --- a/plugins/testData/invalid_structure/v2.0.0/schema.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Example Schema", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "age": { - "type": "integer" - } - }, - "required": ["name", "age"] -} \ No newline at end of file diff --git a/plugins/testData/valid_schemas/schema.json b/plugins/testData/valid_schemas/schema.json deleted file mode 100644 index 3b7396d..0000000 --- a/plugins/testData/valid_schemas/schema.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Example Schema", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "age": { - "type": "integer" - } - }, - "required": ["name", "age"] -} \ No newline at end of file diff --git a/shared/plugin/definition/validator.go b/shared/plugin/definition/validator.go new file mode 100644 index 0000000..776029f --- /dev/null +++ b/shared/plugin/definition/validator.go @@ -0,0 +1,16 @@ +package definition + +import ( + "context" + "net/url" +) + +// Validator interface for schema validation +type Validator interface { + Validate(ctx context.Context, url url.URL, payload []byte) (bool, error) +} + +// ValidatorProvider interface for creating validators +type ValidatorProvider interface { + New(ctx context.Context, config map[string]string) (map[string]Validator, error) +} diff --git a/plugins/implementations/tekuriValidator.so b/shared/plugin/implementations/validator.so similarity index 55% rename from plugins/implementations/tekuriValidator.so rename to shared/plugin/implementations/validator.so index 5394a38..5b01f75 100644 Binary files a/plugins/implementations/tekuriValidator.so and b/shared/plugin/implementations/validator.so differ diff --git a/shared/plugin/implementations/validator/cmd/plugin.go b/shared/plugin/implementations/validator/cmd/plugin.go new file mode 100644 index 0000000..0b3b930 --- /dev/null +++ b/shared/plugin/implementations/validator/cmd/plugin.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + + "beckn-onix/shared/plugin/definition" + "beckn-onix/shared/plugin/implementations/validator" +) + +// ValidatorProvider provides instances of Validator. +type ValidatorProvider struct{} + +// New initializes a new Verifier instance. +func (vp ValidatorProvider) New(ctx context.Context, config map[string]string) (map[string]definition.Validator, error) { + // Create a new Validator instance with the provided configuration + validators, err := validator.New(ctx, config) + if err != nil { + return nil, err + } + + // Convert the map to the expected type + result := make(map[string]definition.Validator) + for key, val := range validators { + result[key] = val + } + + return result, nil +} + +// Provider is the exported symbol that the plugin manager will look for. +var Provider definition.ValidatorProvider = ValidatorProvider{} diff --git a/shared/plugin/implementations/validator/cmd/plugin_test.go b/shared/plugin/implementations/validator/cmd/plugin_test.go new file mode 100644 index 0000000..fc73ff2 --- /dev/null +++ b/shared/plugin/implementations/validator/cmd/plugin_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "errors" + "net/url" + "testing" + + "beckn-onix/shared/plugin/definition" +) + +// MockValidator is a mock implementation of the Validator interface for testing. +type MockValidator struct{} + +func (m *MockValidator) Validate(ctx context.Context, u url.URL, data []byte) (bool, error) { + return true, nil +} + +// Mock New function for testing +func MockNew(ctx context.Context, config map[string]string) (map[string]definition.Validator, error) { + if config["error"] == "true" { + return nil, errors.New("mock error") + } + + return map[string]definition.Validator{ + "validator1": &MockValidator{}, + "validator2": &MockValidator{}, + }, nil +} + +func TestValidatorProvider_New(t *testing.T) { + tests := []struct { + name string + config map[string]string + expectedError string + expectedCount int + }{ + { + name: "Successful initialization", + config: map[string]string{"some_key": "some_value"}, + expectedError: "", + expectedCount: 2, // Expecting 2 mock validators + }, + { + name: "Error during initialization (mock error)", + config: map[string]string{"error": "true"}, + expectedError: "mock error", + expectedCount: 0, + }, + { + name: "Empty config map", + config: map[string]string{}, + expectedError: "", + expectedCount: 2, // Expecting 2 mock validators + }, + { + name: "Non-empty config with invalid key", + config: map[string]string{"invalid_key": "invalid_value"}, + expectedError: "", + expectedCount: 2, // Expecting 2 mock validators + }, + } + + // Using the mock New function directly for testing + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a ValidatorProvider with the mock New function + vp := &ValidatorProvider{} + validators, err := vp.New(context.Background(), tt.config) + + // Check for expected error + if tt.expectedError != "" { + if err == nil || err.Error() != tt.expectedError { + t.Errorf("expected error %q, got %v", tt.expectedError, err) + } + return + } + + // Check for expected number of validators + if len(validators) != tt.expectedCount { + t.Errorf("expected %d validators, got %d", tt.expectedCount, len(validators)) + } + }) + } +} diff --git a/plugins/implementations/plugin_impl.go b/shared/plugin/implementations/validator/validator.go similarity index 58% rename from plugins/implementations/plugin_impl.go rename to shared/plugin/implementations/validator/validator.go index d6a36eb..35ab969 100644 --- a/plugins/implementations/plugin_impl.go +++ b/shared/plugin/implementations/validator/validator.go @@ -1,51 +1,88 @@ -package main +package validator import ( "context" "encoding/json" "fmt" + "net/url" "os" + "path" "path/filepath" "strings" - "beckn-onix/plugins/definitions" - "github.com/santhosh-tekuri/jsonschema/v6" ) -// TekuriValidator implements the Validator interface using the santhosh-tekuri/jsonschema package. -type TekuriValidator struct { - schema *jsonschema.Schema -} - -// TekuriValidatorProvider is responsible for managing and providing access to the JSON schema validators. -type TekuriValidatorProvider struct { +// Validator implements the Validator interface. +type Validator struct { + config map[string]string + schema *jsonschema.Schema SchemaCache map[string]*jsonschema.Schema } -// Validate validates the given data against the schema. -func (v *TekuriValidator) Validate(ctx context.Context, data []byte) error { - var jsonData interface{} - if err := json.Unmarshal(data, &jsonData); err != nil { - return err - } - err := v.schema.Validate(jsonData) +// New creates a new Verifier instance. +func New(ctx context.Context, config map[string]string) (map[string]*Validator, error) { + v := &Validator{config: config} + // Call Initialise function to load schemas and get validators + validators, err := v.Initialise() if err != nil { - // TODO: Integrate with the logging module once it is ready - fmt.Printf("Validation error: %v\n", err) + return nil, fmt.Errorf("failed to initialise validators: %v", err) } - - return err + return validators, nil } -// Initialize initializes the validator provider by compiling all the JSON schema files +// Payload represents the structure of the data payload with context information. +type Payload struct { + Context struct { + Domain string `json:"domain"` + Version string `json:"version"` + } `json:"context"` +} + +// ValidatorProvider provides instances of Validator. +type ValidatorProvider struct{} + +// Validate validates the given data against the schema. +func (v *Validator) Validate(ctx context.Context, url url.URL, payload []byte) (bool, error) { + var payloadData Payload + err := json.Unmarshal(payload, &payloadData) + if err != nil { + return false, fmt.Errorf("failed to parse JSON payload: %v", err) + } + + // Extract domain, version, and endpoint from the payload and uri + domain := payloadData.Context.Domain + version := payloadData.Context.Version + version = fmt.Sprintf("v%s", version) + + endpoint := path.Base(url.String()) + fmt.Println("Handling request for endpoint:", endpoint) + domain = strings.ToLower(domain) + domain = strings.ReplaceAll(domain, ":", "_") + + //schemaFileName := fmt.Sprintf("%s_%s_%s", domain, version, endpoint) + var jsonData interface{} + if err := json.Unmarshal(payload, &jsonData); err != nil { + return false, err + } + err = v.schema.Validate(jsonData) + if err != nil { + // TODO: Integrate with the logging module once it is ready + return false, fmt.Errorf("Validation failed: %v", err) + } + + return true, nil +} + +// Initialise initialises the validator provider by compiling all the JSON schema files // from the specified directory and storing them in a cache. It returns a map of validators // indexed by their schema filenames. -func (vp *TekuriValidatorProvider) Initialize(schemaDir string) (map[string]definitions.Validator, error) { - // Initialize SchemaCache if it's nil - if vp.SchemaCache == nil { - vp.SchemaCache = make(map[string]*jsonschema.Schema) +func (v *Validator) Initialise() (map[string]*Validator, error) { + // Initialize SchemaCache as an empty Map if it's nil + if v.SchemaCache == nil { + v.SchemaCache = make(map[string]*jsonschema.Schema) } + schemaDir := v.config["schema_dir"] // Check if the directory exists and is accessible info, err := os.Stat(schemaDir) if err != nil { @@ -59,7 +96,7 @@ func (vp *TekuriValidatorProvider) Initialize(schemaDir string) (map[string]defi } // Initialize the validatorCache map to store the Validator instances associated with each schema. - validatorCache := make(map[string]definitions.Validator) + validatorCache := make(map[string]*Validator) compiler := jsonschema.NewCompiler() // Helper function to process directories recursively @@ -83,9 +120,6 @@ func (vp *TekuriValidatorProvider) Initialize(schemaDir string) (map[string]defi if err != nil { return fmt.Errorf("failed to compile JSON schema from file %s: %v", entry.Name(), err) } - if compiledSchema == nil { - return fmt.Errorf("compiled schema is nil for file %s", entry.Name()) - } // Use relative path from schemaDir to avoid absolute paths and make schema keys domain/version specific. relativePath, err := filepath.Rel(schemaDir, path) @@ -105,17 +139,18 @@ func (vp *TekuriValidatorProvider) Initialize(schemaDir string) (map[string]defi domain := strings.TrimSpace(parts[0]) version := strings.TrimSpace(parts[1]) schemaFileName := strings.TrimSpace(parts[2]) + schemaFileName = strings.TrimSuffix(schemaFileName, ".json") if domain == "" || version == "" || schemaFileName == "" { return fmt.Errorf("invalid schema file structure, one or more components are empty. Relative path: %s", relativePath) } - // Construct a unique key combining domain, version, and schema name (e.g., ondc_trv10/v2.0.0/schema.json). - uniqueKey := fmt.Sprintf("%s/%s/%s", domain, version, schemaFileName) + // Construct a unique key combining domain, version, and schema name (e.g., ondc_trv10_v2.0.0_schema). + uniqueKey := fmt.Sprintf("%s_%s_%s", domain, version, schemaFileName) // Store the compiled schema in the SchemaCache using the unique key. - vp.SchemaCache[uniqueKey] = compiledSchema + v.SchemaCache[uniqueKey] = compiledSchema // Store the corresponding validator in the validatorCache using the same unique key. - validatorCache[uniqueKey] = &TekuriValidator{schema: compiledSchema} + validatorCache[uniqueKey] = &Validator{schema: compiledSchema} } } return nil @@ -128,12 +163,3 @@ func (vp *TekuriValidatorProvider) Initialize(schemaDir string) (map[string]defi return validatorCache, nil } - -var _ definitions.ValidatorProvider = (*TekuriValidatorProvider)(nil) - -var providerInstance = &TekuriValidatorProvider{} - -// GetProvider returns the ValidatorProvider instance. -func GetProvider() definitions.ValidatorProvider { - return providerInstance -} diff --git a/shared/plugin/implementations/validator/validator_test.go b/shared/plugin/implementations/validator/validator_test.go new file mode 100644 index 0000000..9ae3c3a --- /dev/null +++ b/shared/plugin/implementations/validator/validator_test.go @@ -0,0 +1,291 @@ +package validator + +import ( + "context" + "net/url" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestValidator_Validate(t *testing.T) { + tests := []struct { + name string + url string + payload string + wantValid bool + wantErr string + }{ + { + name: "Valid payload", + url: "http://example.com/endpoint", + payload: `{"context": {"domain": "example", "version": "1.0"}}`, + wantValid: true, + wantErr: "", + }, + { + name: "Invalid JSON payload", + url: "http://example.com/endpoint", + payload: `{"context": {"domain": "example", "version": "1.0"`, + wantValid: false, + wantErr: "failed to parse JSON payload", + }, + { + name: "Schema validation failure", + url: "http://example.com/endpoint", + payload: `{"context": {"domain": "invalid", "version": "1.0"}}`, + wantValid: false, + wantErr: "Validation failed", + }, + } + + // Setup a temporary schema directory for testing + schemaDir := filepath.Join(os.TempDir(), "schemas") + defer os.RemoveAll(schemaDir) + os.MkdirAll(schemaDir, 0755) + + // Create a sample schema file + schemaContent := `{ + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "domain": {"type": "string"}, + "version": {"type": "string"} + }, + "required": ["domain", "version"] + } + }, + "required": ["context"] + }` + schemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") + os.MkdirAll(filepath.Dir(schemaFile), 0755) + os.WriteFile(schemaFile, []byte(schemaContent), 0644) + + config := map[string]string{"schema_dir": schemaDir} + v, err := New(context.Background(), config) + if err != nil { + t.Fatalf("Failed to create validator: %v", err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u, _ := url.Parse(tt.url) + valid, err := v["example_1.0_endpoint"].Validate(context.Background(), *u, []byte(tt.payload)) + if (err != nil && !strings.Contains(err.Error(), tt.wantErr)) || (err == nil && tt.wantErr != "") { + t.Errorf("Error: Validate() returned error = %v, expected error = %v", err, tt.wantErr) + return + } + if valid != tt.wantValid { + t.Errorf("Error: Validate() returned valid = %v, expected valid = %v", valid, tt.wantValid) + } else { + t.Logf("Test %s passed: valid = %v", tt.name, valid) + } + }) + } +} + +func TestValidator_Initialise(t *testing.T) { + tests := []struct { + name string + setupFunc func(schemaDir string) error + wantErr string + }{ + { + name: "Schema directory does not exist", + setupFunc: func(schemaDir string) error { + // Do not create the schema directory + return nil + }, + wantErr: "schema directory does not exist", + }, + { + name: "Schema path is not a directory", + setupFunc: func(schemaDir string) error { + // Create a file instead of a directory + return os.WriteFile(schemaDir, []byte{}, 0644) + }, + wantErr: "provided schema path is not a directory", + }, + { + name: "Invalid schema file structure", + setupFunc: func(schemaDir string) error { + // Create an invalid schema file structure + invalidSchemaFile := filepath.Join(schemaDir, "invalid_schema.json") + os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755) + return os.WriteFile(invalidSchemaFile, []byte(`{}`), 0644) + }, + wantErr: "invalid schema file structure", + }, + { + name: "Failed to compile JSON schema", + setupFunc: func(schemaDir string) error { + // Create a schema file with invalid JSON + invalidSchemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") + os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755) + return os.WriteFile(invalidSchemaFile, []byte(`{invalid json}`), 0644) + }, + wantErr: "failed to compile JSON schema", + }, + { + name: "Invalid schema file structure with empty components", + setupFunc: func(schemaDir string) error { + // Create a schema file with empty domain, version, or schema name + invalidSchemaFile := filepath.Join(schemaDir, "", "1.0", "endpoint.json") + os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755) + return os.WriteFile(invalidSchemaFile, []byte(`{ + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "domain": {"type": "string"}, + "version": {"type": "string"} + }, + "required": ["domain", "version"] + } + }, + "required": ["context"] + }`), 0644) + }, + wantErr: "invalid schema file structure, one or more components are empty", + }, + { + name: "Failed to read directory", + setupFunc: func(schemaDir string) error { + // Create a directory and remove read permissions + os.MkdirAll(schemaDir, 0000) + return nil + }, + wantErr: "failed to read directory", + }, + { + name: "Failed to access schema directory", + setupFunc: func(schemaDir string) error { + // Create a directory and remove access permissions + os.MkdirAll(schemaDir, 0000) + return nil + }, + wantErr: "failed to access schema directory", + }, + { + name: "Valid schema directory", + setupFunc: func(schemaDir string) error { + // Create a valid schema file + validSchemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") + os.MkdirAll(filepath.Dir(validSchemaFile), 0755) + return os.WriteFile(validSchemaFile, []byte(`{ + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "domain": {"type": "string"}, + "version": {"type": "string"} + }, + "required": ["domain", "version"] + } + }, + "required": ["context"] + }`), 0644) + }, + wantErr: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup a temporary schema directory for testing + schemaDir := filepath.Join(os.TempDir(), "schemas") + defer os.RemoveAll(schemaDir) + + // Run the setup function to prepare the test case + if err := tt.setupFunc(schemaDir); err != nil { + t.Fatalf("setupFunc() error = %v", err) + } + + config := map[string]string{"schema_dir": schemaDir} + v := &Validator{config: config} + + _, err := v.Initialise() + if (err != nil && !strings.Contains(err.Error(), tt.wantErr)) || (err == nil && tt.wantErr != "") { + t.Errorf("Error: Initialise() returned error = %v, expected error = %v", err, tt.wantErr) + } else if err == nil { + t.Logf("Test %s passed: validator initialized successfully", tt.name) + } else { + t.Logf("Test %s passed with expected error: %v", tt.name, err) + } + }) + } +} + +func TestValidator_New(t *testing.T) { + tests := []struct { + name string + config map[string]string + setupFunc func(schemaDir string) error + wantErr string + }{ + { + name: "Failed to initialise validators", + config: map[string]string{ + "schema_dir": "/invalid/path", + }, + setupFunc: func(schemaDir string) error { + // Do not create the schema directory + return nil + }, + wantErr: "failed to initialise validators", + }, + { + name: "Valid initialisation", + config: map[string]string{ + "schema_dir": "/valid/path", + }, + setupFunc: func(schemaDir string) error { + // Create a valid schema directory and file + validSchemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") + os.MkdirAll(filepath.Dir(validSchemaFile), 0755) + return os.WriteFile(validSchemaFile, []byte(`{ + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "domain": {"type": "string"}, + "version": {"type": "string"} + }, + "required": ["domain", "version"] + } + }, + "required": ["context"] + }`), 0644) + }, + wantErr: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup a temporary schema directory for testing + schemaDir := tt.config["schema_dir"] + defer os.RemoveAll(schemaDir) + + // Run the setup function to prepare the test case + if err := tt.setupFunc(schemaDir); err != nil { + t.Fatalf("setupFunc() error = %v", err) + } + + _, err := New(context.Background(), tt.config) + if (err != nil && !strings.Contains(err.Error(), tt.wantErr)) || (err == nil && tt.wantErr != "") { + t.Errorf("Error: New() returned error = %v, expected error = %v", err, tt.wantErr) + } else if err == nil { + t.Logf("Test %s passed: validator initialized successfully", tt.name) + } else { + t.Logf("Test %s passed with expected error: %v", tt.name, err) + } + }) + } +} diff --git a/shared/plugin/manager.go b/shared/plugin/manager.go new file mode 100644 index 0000000..8b70b54 --- /dev/null +++ b/shared/plugin/manager.go @@ -0,0 +1,137 @@ +package plugin + +import ( + "beckn-onix/shared/plugin/definition" + "context" + "fmt" + "os" + "path/filepath" + "plugin" + "strings" + + "gopkg.in/yaml.v2" +) + +// ValidationPluginConfig represents configuration details for a plugin. +type ValidationPluginConfig struct { + ID string `yaml:"id"` + Schema SchemaDetails `yaml:"config"` + PluginPath string `yaml:"plugin_path"` +} + +// SchemaDetails contains information about the plugin schema directory. +type SchemaDetails struct { + SchemaDir string `yaml:"schema_dir"` +} + +// Config represents the configuration for the application, including plugin configurations. +type Config struct { + Plugins struct { + ValidationPlugin ValidationPluginConfig `yaml:"validation_plugin"` + } `yaml:"plugins"` +} + +// Manager handles dynamic plugin loading and management. +type Manager struct { + vp definition.ValidatorProvider + validators map[string]definition.Validator + cfg *Config +} + +// NewManager initializes a new Manager with the given configuration file. +func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { + if cfg == nil { + return nil, fmt.Errorf("configuration cannot be nil") + } + + // Load validator plugin + vp, err := provider[definition.ValidatorProvider](cfg.Plugins.ValidationPlugin.PluginPath, cfg.Plugins.ValidationPlugin.ID) + if err != nil { + return nil, fmt.Errorf("failed to load validator plugin: %w", err) + } + if vp == nil { + return nil, fmt.Errorf("validator provider is nil") + } + + // Initialize validator + validatorMap, err := vp.New(ctx, map[string]string{ + "schema_dir": cfg.Plugins.ValidationPlugin.Schema.SchemaDir, + }) + if err != nil { + return nil, fmt.Errorf("failed to initialize validator: %w", err) + } + + // Initialize the validators map + validators := make(map[string]definition.Validator) + for key, validator := range validatorMap { + validators[key] = validator + } + fmt.Println("validators : ", validators) + + return &Manager{vp: vp, validators: validators, cfg: cfg}, nil +} + +// provider loads a plugin dynamically and retrieves its provider instance. +func provider[T any](path string, id string) (T, error) { + var zero T + if len(strings.TrimSpace(id)) == 0 { + return zero, nil + } + + p, err := plugin.Open(pluginPath(path, id)) + if err != nil { + return zero, fmt.Errorf("failed to open plugin %s: %w", id, err) + } + + symbol, err := p.Lookup("Provider") + if err != nil { + return zero, fmt.Errorf("failed to find Provider symbol in plugin %s: %w", id, err) + } + + // Ensure the symbol is of the correct type + prov, ok := symbol.(*T) + if !ok { + return zero, fmt.Errorf("failed to cast Provider for %s", id) + } + + return *prov, nil +} + +// pluginPath constructs the path to the plugin shared object file. +func pluginPath(path, id string) string { + return filepath.Join(path, id+".so") +} + +// Validators retrieves the validation plugin instances. +func (m *Manager) Validators(ctx context.Context) (map[string]definition.Validator, error) { + if m.vp == nil { + return nil, fmt.Errorf("validator plugin provider not loaded") + } + + configMap := map[string]string{ + "schema_dir": m.cfg.Plugins.ValidationPlugin.Schema.SchemaDir, + } + _, err := m.vp.New(ctx, configMap) + if err != nil { + return nil, fmt.Errorf("failed to initialize validator: %w", err) + } + + return m.validators, nil +} + +// LoadConfig loads the configuration from a YAML file. +func LoadConfig(path string) (*Config, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to open config file: %w", err) + } + defer file.Close() + + var cfg Config + decoder := yaml.NewDecoder(file) + if err := decoder.Decode(&cfg); err != nil { + return nil, fmt.Errorf("failed to decode config file: %w", err) + } + + return &cfg, nil +} diff --git a/shared/plugin/plugin.yaml b/shared/plugin/plugin.yaml new file mode 100644 index 0000000..291b0ca --- /dev/null +++ b/shared/plugin/plugin.yaml @@ -0,0 +1,6 @@ +plugins: + validation_plugin: + id: validator + config: + schema_dir: #Directory where the schema files are stored + plugin_path: shared/plugin/implementations/ \ No newline at end of file diff --git a/plugins/testData/directory.json b/shared/plugin/testData/directory.json similarity index 100% rename from plugins/testData/directory.json rename to shared/plugin/testData/directory.json diff --git a/plugins/testData/payloads/cancel.json b/shared/plugin/testData/payloads/cancel.json similarity index 100% rename from plugins/testData/payloads/cancel.json rename to shared/plugin/testData/payloads/cancel.json diff --git a/plugins/testData/payloads/confirm.json b/shared/plugin/testData/payloads/confirm.json similarity index 100% rename from plugins/testData/payloads/confirm.json rename to shared/plugin/testData/payloads/confirm.json diff --git a/plugins/testData/payloads/search.json b/shared/plugin/testData/payloads/search.json similarity index 100% rename from plugins/testData/payloads/search.json rename to shared/plugin/testData/payloads/search.json diff --git a/plugins/testData/payloads/search_extraField.json b/shared/plugin/testData/payloads/search_extraField.json similarity index 100% rename from plugins/testData/payloads/search_extraField.json rename to shared/plugin/testData/payloads/search_extraField.json diff --git a/plugins/testData/payloads/search_missingField.json b/shared/plugin/testData/payloads/search_missingField.json similarity index 100% rename from plugins/testData/payloads/search_missingField.json rename to shared/plugin/testData/payloads/search_missingField.json diff --git a/plugins/testData/payloads/select.json b/shared/plugin/testData/payloads/select.json similarity index 100% rename from plugins/testData/payloads/select.json rename to shared/plugin/testData/payloads/select.json diff --git a/plugins/testData/schema_valid/ondc_trv10/v2.0.0/search.json b/shared/plugin/testData/schema_valid/ondc_trv10/v2.0.0/search.json similarity index 100% rename from plugins/testData/schema_valid/ondc_trv10/v2.0.0/search.json rename to shared/plugin/testData/schema_valid/ondc_trv10/v2.0.0/search.json diff --git a/test.go b/test.go index 5f3eecd..c3d8cb3 100644 --- a/test.go +++ b/test.go @@ -1,34 +1,86 @@ package main import ( - "beckn-onix/plugins" + "beckn-onix/shared/plugin" + "beckn-onix/shared/plugin/definition" + "context" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "net/url" - "strings" ) -// Payload represents the structure of the data payload with context information. +var ( + manager *plugin.Manager + validators map[string]definition.Validator +) + +// Payload represents the structure of the payload with context information. type Payload struct { Context struct { - Domain string `json:"domain"` - Version string `json:"version"` + Action string `json:"action"` + BapID string `json:"bap_id"` + BapURI string `json:"bap_uri"` + BppID string `json:"bpp_id"` + BppURI string `json:"bpp_uri"` + Domain string `json:"domain"` + Location struct { + City struct { + Code string `json:"code"` + } `json:"city"` + Country struct { + Code string `json:"code"` + } `json:"country"` + } `json:"location"` + MessageID string `json:"message_id"` + Timestamp string `json:"timestamp"` + TransactionID string `json:"transaction_id"` + TTL string `json:"ttl"` + Version string `json:"version"` } `json:"context"` + Message struct { + CancellationReasonID string `json:"cancellation_reason_id"` + Descriptor struct { + Code string `json:"code"` + Name string `json:"name"` + } `json:"descriptor"` + OrderID string `json:"order_id"` + } `json:"message"` } func main() { - http.HandleFunc("/", validateHandler) + var err error + // Load the configuration + config, err := plugin.LoadConfig("shared/plugin/plugin.yaml") + if err != nil { + log.Fatalf("Failed to load plugins configuration: %v", err) + } + + // Initialize the plugin manager + manager, err = plugin.NewManager(context.Background(), config) + if err != nil { + log.Fatalf("Failed to create PluginManager: %v", err) + } + + // Get the validators map + validators, err := manager.Validators(context.Background()) + if err != nil { + log.Fatalf("Failed to get validators: %v", err) + } + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + validateHandler(w, r, validators) + }) fmt.Println("Starting server on port 8084...") - err := http.ListenAndServe(":8084", nil) + err = http.ListenAndServe(":8084", nil) if err != nil { log.Fatalf("Server failed to start: %v", err) } } -func validateHandler(w http.ResponseWriter, r *http.Request) { +func validateHandler(w http.ResponseWriter, r *http.Request, validators map[string]definition.Validator) { if r.Method != http.MethodPost { http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) return @@ -47,39 +99,22 @@ func validateHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Failed to read payload data", http.StatusInternalServerError) return } + fmt.Println("printing payload data", string(payloadData)) - // Initialize an instance of Payload struct var payload Payload - err1 := json.Unmarshal(payloadData, &payload) - if err1 != nil { - http.Error(w, "Failed to parse JSON payload", http.StatusBadRequest) - return - } - - // Extract the domain, version, and endpoint from the payload and URL - domain := payload.Context.Domain - version := payload.Context.Version - version = fmt.Sprintf("v%s", version) - - endpoint := strings.Trim(u.Path, "/") - fmt.Println("Handling request for endpoint:", endpoint) - domain = strings.ToLower(domain) - domain = strings.ReplaceAll(domain, ":", "_") - - schemaFileName := fmt.Sprintf("%s/%s/%s.json", domain, version, endpoint) - - pluginsConfig, err := plugins.LoadPluginsConfig("plugins/config.yaml") + err = json.Unmarshal(payloadData, &payload) if err != nil { - http.Error(w, "Failed to load plugins configuration", http.StatusInternalServerError) + log.Printf("Failed to parse JSON payload: %v", err) + http.Error(w, fmt.Sprintf("Failed to parse JSON payload: %v", err), http.StatusBadRequest) return } - debug := true - _, validators, err := plugins.NewValidatorProvider(pluginsConfig, debug) - if err != nil { - http.Error(w, "Failed to create PluginManager", http.StatusInternalServerError) + // Validate that the domain and version fields are not empty + if payload.Context.Domain == "" || payload.Context.Version == "" { + http.Error(w, "Invalid payload: domain and version are required fields", http.StatusBadRequest) return } + schemaFileName := "ondc_trv10_v2.0.0_cancel" validator, exists := validators[schemaFileName] if !exists { @@ -87,10 +122,12 @@ func validateHandler(w http.ResponseWriter, r *http.Request) { return } - ctx := r.Context() - err = validator.Validate(ctx, payloadData) + ctx := context.Background() + valid, err := validator.Validate(ctx, *u, payloadData) if err != nil { http.Error(w, fmt.Sprintf("Document validation failed: %v", err), http.StatusBadRequest) + } else if !valid { + http.Error(w, "Document validation failed", http.StatusBadRequest) } else { w.WriteHeader(http.StatusOK) w.Write([]byte("Document validation succeeded!"))