diff --git a/.gitignore b/.gitignore index b546895..12018c1 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,13 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# Ignore compiled shared object files +*.so + +# Ignore coverage output files +coverage.out +coverage.html + +# Ignore the schema directory used for testing +/schemas/ \ No newline at end of file diff --git a/go.mod b/go.mod index 7ea3206..f050463 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module beckn-onix +module github.com/beckn/beckn-onix go 1.23.4 diff --git a/pkg/plugin/definition/schemaValidator.go b/pkg/plugin/definition/schemaValidator.go new file mode 100644 index 0000000..47aa18c --- /dev/null +++ b/pkg/plugin/definition/schemaValidator.go @@ -0,0 +1,38 @@ +package definition + +import ( + "context" + "fmt" + "net/url" + "strings" +) + +// SchemaValError represents a single schema validation failure. +type SchemaValError struct { + Path string + Message string +} + +// SchemaValidationErr represents a collection of schema validation failures. +type SchemaValidationErr struct { + Errors []SchemaValError +} + +// Validator interface for schema validation. +type SchemaValidator interface { + Validate(ctx context.Context, url *url.URL, payload []byte) error +} + +// ValidatorProvider interface for creating validators. +type SchemaValidatorProvider interface { + New(ctx context.Context, config map[string]string) (SchemaValidator, func() error, error) +} + +// Error implements the error interface for SchemaValidationErr. +func (e *SchemaValidationErr) Error() string { + var errorMessages []string + for _, err := range e.Errors { + errorMessages = append(errorMessages, fmt.Sprintf("%s: %s", err.Path, err.Message)) + } + return strings.Join(errorMessages, "; ") +} diff --git a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go new file mode 100644 index 0000000..fbe61b7 --- /dev/null +++ b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "errors" + + definition "github.com/beckn/beckn-onix/pkg/plugin/definition" + validator "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemaValidator" +) + +// ValidatorProvider provides instance of Validator. +type schemaValidatorProvider struct{} + +// New initializes a new Verifier instance. +func (vp schemaValidatorProvider) New(ctx context.Context, config map[string]string) (definition.SchemaValidator, func() error, error) { + if ctx == nil { + return nil, nil, errors.New("context cannot be nil") + } + + // Extract schema_dir from the config map + schemaDir, ok := config["schema_dir"] + if !ok || schemaDir == "" { + return nil, nil, errors.New("config must contain 'schema_dir'") + } + + // Create a new Validator instance with the provided configuration + return validator.New(ctx, &validator.Config{ + SchemaDir: schemaDir, // Pass the schemaDir to the validator.Config + }) +} + +// Provider is the exported symbol that the plugin manager will look for. +var Provider definition.SchemaValidatorProvider = &schemaValidatorProvider{} diff --git a/pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go b/pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go new file mode 100644 index 0000000..99914a2 --- /dev/null +++ b/pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go @@ -0,0 +1,148 @@ +package main + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" +) + +// setupTestSchema creates a temporary directory and writes a sample schema file. +func setupTestSchema(t *testing.T) string { + t.Helper() + + // Create a temporary directory for the schema + schemaDir, err := os.MkdirTemp("", "schemas") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + + // Create the directory structure for the schema file + schemaFilePath := filepath.Join(schemaDir, "example", "1.0", "test_schema.json") + if err := os.MkdirAll(filepath.Dir(schemaFilePath), 0755); err != nil { + t.Fatalf("Failed to create schema directory structure: %v", err) + } + + // Define a sample schema + schemaContent := `{ + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "domain": {"type": "string"}, + "version": {"type": "string"} + }, + "required": ["domain", "version"] + } + }, + "required": ["context"] + }` + + // Write the schema to the file + if err := os.WriteFile(schemaFilePath, []byte(schemaContent), 0644); err != nil { + t.Fatalf("Failed to write schema file: %v", err) + } + + return schemaDir +} + +// TestValidatorProviderSuccess tests successful ValidatorProvider implementation. +func TestValidatorProviderSuccess(t *testing.T) { + schemaDir := setupTestSchema(t) + defer os.RemoveAll(schemaDir) + + // Define test cases. + tests := []struct { + name string + ctx context.Context + config map[string]string + expectedError string + }{ + { + name: "Valid schema directory", + ctx: context.Background(), // Valid context + config: map[string]string{"schema_dir": schemaDir}, + expectedError: "", + }, + } + + // Test using table-driven tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + vp := schemaValidatorProvider{} + validator, close, err := vp.New(tt.ctx, tt.config) + + // Ensure no error occurred + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // Ensure the validator is not nil + if validator == nil { + t.Error("expected a non-nil validator, got nil") + } + + // Ensure the close function is not nil + if close == nil { + t.Error("expected a non-nil close function, got nil") + } + + // Test the close function + if err := close(); err != nil { + t.Errorf("close function returned an error: %v", err) + } + }) + } +} + +// TestValidatorProviderSuccess tests cases where ValidatorProvider creation should fail. +func TestValidatorProviderFailure(t *testing.T) { + schemaDir := setupTestSchema(t) + defer os.RemoveAll(schemaDir) + + // Define test cases. + tests := []struct { + name string + ctx context.Context + config map[string]string + expectedError string + }{ + { + name: "Invalid schema directory", + ctx: context.Background(), // Valid context + config: map[string]string{"schema_dir": "/invalid/dir"}, + expectedError: "failed to initialise validator: schema directory does not exist: /invalid/dir", + }, + { + name: "Nil context", + ctx: nil, // Nil context + config: map[string]string{"schema_dir": schemaDir}, + expectedError: "context cannot be nil", + }, + } + + // Test using table-driven tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + vp := schemaValidatorProvider{} + _, _, err := vp.New(tt.ctx, tt.config) + + // Check for expected error + if tt.expectedError != "" { + if err == nil || !strings.Contains(err.Error(), tt.expectedError) { + t.Errorf("expected error %q, got %v", tt.expectedError, err) + } + return + } + + // Ensure no error occurred + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + }) + } +} diff --git a/pkg/plugin/implementation/schemaValidator/schemaValidator.go b/pkg/plugin/implementation/schemaValidator/schemaValidator.go new file mode 100644 index 0000000..7f57c4e --- /dev/null +++ b/pkg/plugin/implementation/schemaValidator/schemaValidator.go @@ -0,0 +1,201 @@ +package schemaValidator + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + definition "github.com/beckn/beckn-onix/pkg/plugin/definition" + + "github.com/santhosh-tekuri/jsonschema/v6" +) + +// 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"` +} + +// Validator implements the Validator interface. +type SchemaValidator struct { + config *Config + schemaCache map[string]*jsonschema.Schema +} + +type Config struct { + SchemaDir string +} + +// New creates a new ValidatorProvider instance. +func New(ctx context.Context, config *Config) (*SchemaValidator, func() error, error) { + // Check if config is nil + if config == nil { + return nil, nil, fmt.Errorf("config cannot be nil") + } + v := &SchemaValidator{ + config: config, + schemaCache: make(map[string]*jsonschema.Schema), + } + + // Call Initialise function to load schemas and get validators + if err := v.Initialise(); err != nil { + return nil, nil, fmt.Errorf("failed to initialise validator: %v", err) + } + return v, v.Close, nil +} + +// Validate validates the given data against the schema. +func (v *SchemaValidator) Validate(ctx context.Context, url *url.URL, data []byte) error { + var payloadData payload + err := json.Unmarshal(data, &payloadData) + if err != nil { + return fmt.Errorf("failed to parse JSON payload: %v", err) + } + + // Extract domain, version, and endpoint from the payload and uri. + cxt_domain := payloadData.Context.Domain + version := payloadData.Context.Version + version = fmt.Sprintf("v%s", version) + + endpoint := path.Base(url.String()) + // ToDo Add debug log here + fmt.Println("Handling request for endpoint:", endpoint) + domain := strings.ToLower(cxt_domain) + domain = strings.ReplaceAll(domain, ":", "_") + + // Construct the schema file name. + schemaFileName := fmt.Sprintf("%s_%s_%s", domain, version, endpoint) + + // Retrieve the schema from the cache. + schema, exists := v.schemaCache[schemaFileName] + if !exists { + return fmt.Errorf("schema not found for domain: %s", schemaFileName) + } + + var jsonData any + if err := json.Unmarshal(data, &jsonData); err != nil { + return fmt.Errorf("failed to parse JSON data: %v", err) + } + err = schema.Validate(jsonData) + if err != nil { + // Handle schema validation errors + if validationErr, ok := err.(*jsonschema.ValidationError); ok { + // Convert validation errors into an array of SchemaValError + var schemaErrors []definition.SchemaValError + for _, cause := range validationErr.Causes { + // Extract the path and message from the validation error + path := strings.Join(cause.InstanceLocation, ".") // JSON path to the invalid field + message := cause.Error() // Validation error message + + // Append the error to the schemaErrors array + schemaErrors = append(schemaErrors, definition.SchemaValError{ + Path: path, + Message: message, + }) + } + // Return the array of schema validation errors + return &definition.SchemaValidationErr{Errors: schemaErrors} + } + // Return a generic error for non-validation errors + return fmt.Errorf("validation failed: %v", err) + } + + // Return nil if validation succeeds + return nil +} + +// ValidatorProvider provides instances of Validator. +type ValidatorProvider struct{} + +// Initialise initialises the validator provider by compiling all the JSON schema files +// from the specified directory and storing them in a cache indexed by their schema filenames. +func (v *SchemaValidator) Initialise() error { + schemaDir := v.config.SchemaDir + // Check if the directory exists and is accessible. + info, err := os.Stat(schemaDir) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("schema directory does not exist: %s", schemaDir) + } + return fmt.Errorf("failed to access schema directory: %v", err) + } + if !info.IsDir() { + return fmt.Errorf("provided schema path is not a directory: %s", schemaDir) + } + + compiler := jsonschema.NewCompiler() + + // Helper function to process directories recursively. + var processDir func(dir string) error + processDir = func(dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + return fmt.Errorf("failed to read directory: %v", err) + } + + for _, entry := range entries { + path := filepath.Join(dir, entry.Name()) + if entry.IsDir() { + // Recursively process subdirectories. + if err := processDir(path); err != nil { + return err + } + } else if filepath.Ext(entry.Name()) == ".json" { + // Process JSON files. + compiledSchema, err := compiler.Compile(path) + if err != nil { + return fmt.Errorf("failed to compile JSON schema from file %s: %v", entry.Name(), err) + } + + // Use relative path from schemaDir to avoid absolute paths and make schema keys domain/version specific. + relativePath, err := filepath.Rel(schemaDir, path) + if err != nil { + return fmt.Errorf("failed to get relative path for file %s: %v", entry.Name(), err) + } + // Split the relative path to get domain, version, and schema. + parts := strings.Split(relativePath, string(os.PathSeparator)) + + // Ensure that the file path has at least 3 parts: domain, version, and schema file. + if len(parts) < 3 { + return fmt.Errorf("invalid schema file structure, expected domain/version/schema.json but got: %s", relativePath) + } + + // Extract domain, version, and schema filename from the parts. + // Validate that the extracted parts are non-empty. + 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). + uniqueKey := fmt.Sprintf("%s_%s_%s", domain, version, schemaFileName) + // Store the compiled schema in the SchemaCache using the unique key. + v.schemaCache[uniqueKey] = compiledSchema + } + } + return nil + } + + // Start processing from the root schema directory. + if err := processDir(schemaDir); err != nil { + return fmt.Errorf("failed to read schema directory: %v", err) + } + + return nil +} + +// Close releases resources (mock implementation returning nil). +func (v *SchemaValidator) Close() error { + return nil +} diff --git a/shared/plugin/implementations/validator/validator_test.go b/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go similarity index 52% rename from shared/plugin/implementations/validator/validator_test.go rename to pkg/plugin/implementation/schemaValidator/schemaValidator_test.go index 3a95761..1fe54d5 100644 --- a/shared/plugin/implementations/validator/validator_test.go +++ b/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go @@ -1,17 +1,97 @@ -package validator +package schemaValidator import ( - "beckn-onix/shared/plugin/definition" "context" - "fmt" "net/url" "os" "path/filepath" "strings" "testing" + + "github.com/beckn/beckn-onix/pkg/plugin/definition" ) -func TestValidator_Validate(t *testing.T) { +// setupTestSchema creates a temporary directory and writes a sample schema file. +func setupTestSchema(t *testing.T) string { + t.Helper() + + // Create a temporary directory for the schema + schemaDir, err := os.MkdirTemp("", "schemas") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + + // Create the directory structure for the schema file + schemaFilePath := filepath.Join(schemaDir, "example", "v1.0", "endpoint.json") + if err := os.MkdirAll(filepath.Dir(schemaFilePath), 0755); err != nil { + t.Fatalf("Failed to create schema directory structure: %v", err) + } + + // Define a sample schema + schemaContent := `{ + "type": "object", + "properties": { + "context": { + "type": "object", + "properties": { + "domain": {"type": "string"}, + "version": {"type": "string"}, + "action": {"type": "string"} + }, + "required": ["domain", "version", "action"] + } + }, + "required": ["context"] + }` + + // Write the schema to the file + if err := os.WriteFile(schemaFilePath, []byte(schemaContent), 0644); err != nil { + t.Fatalf("Failed to write schema file: %v", err) + } + + return schemaDir +} + +func TestValidator_Validate_Success(t *testing.T) { + tests := []struct { + name string + url string + payload string + wantValid bool + }{ + { + name: "Valid payload", + url: "http://example.com/endpoint", + payload: `{"context": {"domain": "example", "version": "1.0", "action": "endpoint"}}`, + wantValid: true, + }, + } + + // Setup a temporary schema directory for testing + schemaDir := setupTestSchema(t) + defer os.RemoveAll(schemaDir) + + config := &Config{SchemaDir: 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.Validate(context.Background(), u, []byte(tt.payload)) + if err != (definition.SchemaValError{}) { + t.Errorf("Unexpected error: %v", err) + } + if valid != tt.wantValid { + t.Errorf("Error: Validate() returned valid = %v, expected valid = %v", valid, tt.wantValid) + } + }) + } +} + +func TestValidator_Validate_Failure(t *testing.T) { tests := []struct { name string url string @@ -19,13 +99,6 @@ func TestValidator_Validate(t *testing.T) { 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", @@ -36,54 +109,39 @@ func TestValidator_Validate(t *testing.T) { { name: "Schema validation failure", url: "http://example.com/endpoint", - payload: `{"context": {"domain": "invalid", "version": "1.0"}}`, + payload: `{"context": {"domain": "example", "version": "1.0"}}`, wantValid: false, wantErr: "Validation failed", }, + { + name: "Schema not found", + url: "http://example.com/unknown_endpoint", + payload: `{"context": {"domain": "example", "version": "1.0"}}`, + wantValid: false, + wantErr: "schema not found for domain", + }, } // Setup a temporary schema directory for testing - schemaDir := filepath.Join(os.TempDir(), "schemas") + schemaDir := setupTestSchema(t) 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 != (definition.Error{}) { + config := &Config{SchemaDir: 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 != (definition.Error{}) && !strings.Contains(err.Message, tt.wantErr)) || (err == (definition.Error{}) && tt.wantErr != "") { + valid, err := v.Validate(context.Background(), u, []byte(tt.payload)) + if (err != (definition.SchemaValError{}) && !strings.Contains(err.Message, tt.wantErr)) || (err == (definition.SchemaValError{}) && 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) + t.Errorf("Validate() returned valid = %v, expected valid = %v", valid, tt.wantValid) } }) } @@ -99,7 +157,7 @@ func TestValidator_Initialise(t *testing.T) { name: "Schema directory does not exist", setupFunc: func(schemaDir string) error { // Do not create the schema directory - return fmt.Errorf("schema directory does not exist: %s", schemaDir) + return nil }, wantErr: "schema directory does not exist", @@ -117,7 +175,9 @@ func TestValidator_Initialise(t *testing.T) { setupFunc: func(schemaDir string) error { // Create an invalid schema file structure invalidSchemaFile := filepath.Join(schemaDir, "invalid_schema.json") - os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755) + if err := os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755); err != nil { + t.Fatalf("Failed to create directory: %v", err) + } return os.WriteFile(invalidSchemaFile, []byte(`{}`), 0644) }, wantErr: "invalid schema file structure", @@ -127,7 +187,9 @@ func TestValidator_Initialise(t *testing.T) { 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) + if err := os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755); err != nil { + t.Fatalf("Failed to create directory: %v", err) + } return os.WriteFile(invalidSchemaFile, []byte(`{invalid json}`), 0644) }, wantErr: "failed to compile JSON schema", @@ -137,7 +199,9 @@ func TestValidator_Initialise(t *testing.T) { 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) + if err := os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755); err != nil { + t.Fatalf("Failed to create directory: %v", err) + } return os.WriteFile(invalidSchemaFile, []byte(`{ "type": "object", "properties": { @@ -153,32 +217,27 @@ func TestValidator_Initialise(t *testing.T) { "required": ["context"] }`), 0644) }, - wantErr: "invalid schema file structure, one or more components are empty", + wantErr: "failed to read schema directory: invalid schema file structure, expected domain/version/schema.json but got: 1.0/endpoint.json", }, { name: "Failed to read directory", setupFunc: func(schemaDir string) error { // Create a directory and remove read permissions - os.MkdirAll(schemaDir, 0000) + if err := os.MkdirAll(schemaDir, 0000); err != nil { + t.Fatalf("Failed to create directory: %v", err) + } 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) + if err := os.MkdirAll(filepath.Dir(validSchemaFile), 0755); err != nil { + t.Fatalf("Failed to create directory: %v", err) + } return os.WriteFile(validSchemaFile, []byte(`{ "type": "object", "properties": { @@ -209,13 +268,13 @@ func TestValidator_Initialise(t *testing.T) { t.Fatalf("setupFunc() error = %v", err) } - config := map[string]string{"schema_dir": schemaDir} + config := &Config{SchemaDir: schemaDir} v := &Validator{config: config} - _, err := v.Initialise() - if (err != (definition.Error{}) && !strings.Contains(err.Message, tt.wantErr)) || (err == (definition.Error{}) && tt.wantErr != "") { + 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 == (definition.Error{}) { + } 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) @@ -224,68 +283,78 @@ func TestValidator_Initialise(t *testing.T) { } } -func TestValidator_New(t *testing.T) { +func TestValidator_New_Success(t *testing.T) { + schemaDir := setupTestSchema(t) + defer os.RemoveAll(schemaDir) + + config := &Config{SchemaDir: schemaDir} + _, _, err := New(context.Background(), config) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestValidator_New_Failure(t *testing.T) { tests := []struct { name string - config map[string]string + config *Config setupFunc func(schemaDir string) error wantErr string }{ + { + name: "Config is nil", + config: nil, + setupFunc: func(schemaDir string) error { + return nil + }, + wantErr: "config cannot be nil", + }, + { + name: "Config is empty", + config: &Config{}, + setupFunc: func(schemaDir string) error { + return nil + }, + wantErr: "config must contain 'schema_dir'", + }, + { + name: "schema_dir is empty", + config: &Config{SchemaDir: ""}, + setupFunc: func(schemaDir string) error { + return nil + }, + wantErr: "config must contain 'schema_dir'", + }, { name: "Failed to initialise validators", - config: map[string]string{ - "schema_dir": "/invalid/path", + config: &Config{ + SchemaDir: "/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: "", + wantErr: "failed to initialise validator", }, } 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) + // Run the setup function if provided + if tt.setupFunc != nil { + schemaDir := "" + if tt.config != nil { + schemaDir = tt.config.SchemaDir + } + if err := tt.setupFunc(schemaDir); err != nil { + t.Fatalf("Setup function failed: %v", err) + } } - _, err := New(context.Background(), tt.config) - if (err != (definition.Error{}) && !strings.Contains(err.Message, tt.wantErr)) || (err == (definition.Error{}) && tt.wantErr != "") { + // Call the New function with the test config + _, _, 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 == (definition.Error{}) { - 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/pkg/plugin/manager.go similarity index 51% rename from shared/plugin/manager.go rename to pkg/plugin/manager.go index c257826..9b0c612 100644 --- a/shared/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -1,7 +1,6 @@ package plugin import ( - "beckn-onix/shared/plugin/definition" "context" "fmt" "os" @@ -9,33 +8,47 @@ import ( "plugin" "strings" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "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"` +// Config represents the plugin manager configuration. +type Config struct { + Root string `yaml:"root"` + Validator PluginConfig `yaml:"schema_validator"` } +// PluginConfig represents configuration details for a plugin. +type PluginConfig struct { + ID string `yaml:"id"` + Config map[string]string `yaml:"config"` +} + +// // 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"` -} +// // 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 + vp definition.SchemaValidatorProvider + // validators definition.Validator + cfg *Config } // NewManager initializes a new Manager with the given configuration file. @@ -45,7 +58,7 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { } // Load validator plugin - vp, err := provider[definition.ValidatorProvider](cfg.Plugins.ValidationPlugin.PluginPath, cfg.Plugins.ValidationPlugin.ID) + vp, err := provider[definition.SchemaValidatorProvider](cfg.Root, cfg.Validator.ID) if err != nil { return nil, fmt.Errorf("failed to load validator plugin: %w", err) } @@ -53,21 +66,21 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { return nil, fmt.Errorf("validator provider is nil") } - // Initialize validator - validatorMap, defErr := vp.New(ctx, map[string]string{ - "schema_dir": cfg.Plugins.ValidationPlugin.Schema.SchemaDir, - }) - if defErr != (definition.Error{}) { - return nil, fmt.Errorf("failed to initialize validator: %v", defErr) - } + // // Initialize validator + // validatorMap, defErr := vp.New(ctx, map[string]string{ + // "schema_dir": cfg.Plugins.ValidationPlugin.Schema.SchemaDir, + // }) + // if defErr != nil { + // return nil, fmt.Errorf("failed to initialize validator: %v", defErr) + // } - // Initialize the validators map - validators := make(map[string]definition.Validator) - for key, validator := range validatorMap { - validators[key] = validator - } + // // Initialize the validators map + // validators := make(map[string]definition.Validator) + // for key, validator := range validatorMap { + // validators[key] = validator + // } - return &Manager{vp: vp, validators: validators, cfg: cfg}, nil + return &Manager{vp: vp, cfg: cfg}, nil } // provider loads a plugin dynamically and retrieves its provider instance. @@ -96,27 +109,23 @@ func provider[T any](path string, id string) (T, error) { return *prov, nil } -// pluginPath constructs the path to the plugin shared object file. +// pluginPath constructs the path to the plugin pkg 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, definition.Error) { +func (m *Manager) Validator(ctx context.Context) (definition.SchemaValidator, func() error, error) { if m.vp == nil { - return nil, definition.Error{Message: "validator plugin provider not loaded"} + return nil, 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 != (definition.Error{}) { + validator, close, err := m.vp.New(ctx, m.cfg.Validator.Config) + if err != nil { - return nil, definition.Error{Message: fmt.Sprintf("failed to initialize validator: %v", err)} + return nil, nil, fmt.Errorf("failed to initialize validator: %v", err) } - - return m.validators, definition.Error{} + return validator, close, nil } // LoadConfig loads the configuration from a YAML file. diff --git a/pkg/plugin/plugin.yaml b/pkg/plugin/plugin.yaml new file mode 100644 index 0000000..86947d8 --- /dev/null +++ b/pkg/plugin/plugin.yaml @@ -0,0 +1,6 @@ +root: pkg/plugin/implementation/ +schema_validator: + id: validator + config: + schema_dir: schemas #Directory where the schema files are stored + plugin_path: pkg/plugin/implementations/ diff --git a/shared/plugin/testData/directory.json b/pkg/plugin/testData/directory.json similarity index 100% rename from shared/plugin/testData/directory.json rename to pkg/plugin/testData/directory.json diff --git a/shared/plugin/testData/payloads/cancel.json b/pkg/plugin/testData/payloads/cancel.json similarity index 100% rename from shared/plugin/testData/payloads/cancel.json rename to pkg/plugin/testData/payloads/cancel.json diff --git a/shared/plugin/testData/payloads/confirm.json b/pkg/plugin/testData/payloads/confirm.json similarity index 100% rename from shared/plugin/testData/payloads/confirm.json rename to pkg/plugin/testData/payloads/confirm.json diff --git a/shared/plugin/testData/payloads/search.json b/pkg/plugin/testData/payloads/search.json similarity index 100% rename from shared/plugin/testData/payloads/search.json rename to pkg/plugin/testData/payloads/search.json diff --git a/shared/plugin/testData/payloads/search_extraField.json b/pkg/plugin/testData/payloads/search_extraField.json similarity index 100% rename from shared/plugin/testData/payloads/search_extraField.json rename to pkg/plugin/testData/payloads/search_extraField.json diff --git a/shared/plugin/testData/payloads/search_missingField.json b/pkg/plugin/testData/payloads/search_missingField.json similarity index 100% rename from shared/plugin/testData/payloads/search_missingField.json rename to pkg/plugin/testData/payloads/search_missingField.json diff --git a/shared/plugin/testData/payloads/select.json b/pkg/plugin/testData/payloads/select.json similarity index 100% rename from shared/plugin/testData/payloads/select.json rename to pkg/plugin/testData/payloads/select.json diff --git a/shared/plugin/testData/schema_valid/ondc_trv10/v2.0.0/search.json b/pkg/plugin/testData/schema_valid/ondc_trv10/v2.0.0/search.json similarity index 100% rename from shared/plugin/testData/schema_valid/ondc_trv10/v2.0.0/search.json rename to pkg/plugin/testData/schema_valid/ondc_trv10/v2.0.0/search.json diff --git a/shared/plugin/definition/validator.go b/shared/plugin/definition/validator.go deleted file mode 100644 index 21a1bf8..0000000 --- a/shared/plugin/definition/validator.go +++ /dev/null @@ -1,22 +0,0 @@ -package definition - -import ( - "context" - "net/url" -) - -// Error struct for validation errors -type Error struct { - Path string - Message string -} - -// 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/shared/plugin/implementations/validator.so b/shared/plugin/implementations/validator.so deleted file mode 100644 index ebd63b3..0000000 Binary files a/shared/plugin/implementations/validator.so and /dev/null differ diff --git a/shared/plugin/implementations/validator/cmd/plugin.go b/shared/plugin/implementations/validator/cmd/plugin.go deleted file mode 100644 index 13dab92..0000000 --- a/shared/plugin/implementations/validator/cmd/plugin.go +++ /dev/null @@ -1,25 +0,0 @@ -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, definition.Error) { - // Create a new Validator instance with the provided configuration - validators, err := validator.New(ctx, config) - if err != (definition.Error{}) { - return nil, definition.Error{Path: "", Message: err.Message} - } - - return validators, definition.Error{} -} - -// 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 deleted file mode 100644 index 525a761..0000000 --- a/shared/plugin/implementations/validator/cmd/plugin_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "io/ioutil" - "net/url" - "os" - "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, definition.Error) { - return true, definition.Error{} -} - -// Mock New function for testing -func MockNew(ctx context.Context, config map[string]string) (map[string]definition.Validator, error) { - // If the config has the error flag set to "true", return an error - if config["error"] == "true" { - return nil, errors.New("mock error") - } - - // If schema_dir is set, print it out for debugging purposes - if schemaDir, ok := config["schema_dir"]; ok { - // You could add more logic to handle the schema_dir, for now we just print it - fmt.Println("Using schema directory:", schemaDir) - } - - // Return a map of mock validators - return map[string]definition.Validator{ - "validator1": &MockValidator{}, - "validator2": &MockValidator{}, - }, nil -} - -// New method for ValidatorProvider, uses MockNew for creating mock validators -func New(ctx context.Context, config map[string]string) (map[string]definition.Validator, definition.Error) { - validators, err := MockNew(ctx, config) - if err != nil { - return nil, definition.Error{Message: err.Error()} - } - return validators, definition.Error{} -} - -func TestValidatorProvider(t *testing.T) { - // Create a temporary directory for the schema - schemaDir, err := ioutil.TempDir("", "schemas") - if err != nil { - t.Fatalf("Failed to create temp directory: %v", err) - } - defer os.RemoveAll(schemaDir) - - // Create a temporary JSON schema file - schemaFile := fmt.Sprintf("%s/test_schema.json", schemaDir) - schemaContent := `{"type": "object", "properties": {"name": {"type": "string"}}}` - if err := ioutil.WriteFile(schemaFile, []byte(schemaContent), 0644); err != nil { - t.Fatalf("Failed to write schema file: %v", err) - } - - // Define test cases - tests := []struct { - name string - config map[string]string - expectedError string - expectedCount int - }{ - { - name: "Valid schema directory", - config: map[string]string{"schema_dir": schemaDir}, // Use schemaDir instead of tempDir - expectedError: "", - expectedCount: 2, // Expecting 2 mock validators - }, - { - name: "Invalid schema directory", - config: map[string]string{"schema_dir": "/invalid/dir"}, - expectedError: "failed to initialise validators: {/invalid/dir schema directory does not exist}", - expectedCount: 0, - }, - } - - // Test using table-driven tests - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - vp := ValidatorProvider{} - validators, err := vp.New(context.Background(), tt.config) - - // Check for expected error - if tt.expectedError != "" { - if err == (definition.Error{}) || err.Message != 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/shared/plugin/implementations/validator/validator.go b/shared/plugin/implementations/validator/validator.go deleted file mode 100644 index 57cf4bb..0000000 --- a/shared/plugin/implementations/validator/validator.go +++ /dev/null @@ -1,166 +0,0 @@ -package validator - -import ( - "context" - "encoding/json" - "fmt" - "net/url" - "os" - "path" - "path/filepath" - "strings" - - "beckn-onix/shared/plugin/definition" - - "github.com/santhosh-tekuri/jsonschema/v6" -) - -// 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"` -} - -// Validator implements the Validator interface. -type Validator struct { - config map[string]string - schema *jsonschema.Schema - SchemaCache map[string]*jsonschema.Schema -} - -// New creates a new ValidatorProvider instance. -func New(ctx context.Context, config map[string]string) (map[string]definition.Validator, definition.Error) { - v := &Validator{config: config} - // Call Initialise function to load schemas and get validators - validators, err := v.Initialise() - if err != (definition.Error{}) { - return nil, definition.Error{Message: fmt.Sprintf("failed to initialise validators: %v", err)} - } - return validators, definition.Error{} -} - -// Validate validates the given data against the schema. -func (v *Validator) Validate(ctx context.Context, url *url.URL, payload []byte) (bool, definition.Error) { - var payloadData Payload - err := json.Unmarshal(payload, &payloadData) - if err != nil { - return false, definition.Error{Path: "", Message: fmt.Sprintf("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, ":", "_") - - var jsonData interface{} - if err := json.Unmarshal(payload, &jsonData); err != nil { - return false, definition.Error{Path: "", Message: err.Error()} - } - err = v.schema.Validate(jsonData) - if err != nil { - // TODO: Integrate with the logging module once it is ready - return false, definition.Error{Path: "", Message: fmt.Sprintf("Validation failed: %v", err)} - } - - return true, definition.Error{} -} - -// ValidatorProvider provides instances of Validator. -type ValidatorProvider struct{} - -// 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 (v *Validator) Initialise() (map[string]definition.Validator, definition.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 { - if os.IsNotExist(err) { - return nil, definition.Error{Path: schemaDir, Message: "schema directory does not exist"} - } - return nil, definition.Error{Path: schemaDir, Message: fmt.Sprintf("failed to access schema directory: %v", err)} - } - if !info.IsDir() { - return nil, definition.Error{Path: schemaDir, Message: "provided schema path is not a directory"} - } - - // Initialize the validatorCache map to store the Validator instances associated with each schema. - validatorCache := make(map[string]definition.Validator) - compiler := jsonschema.NewCompiler() - - // Helper function to process directories recursively - var processDir func(dir string) definition.Error - processDir = func(dir string) definition.Error { - entries, err := os.ReadDir(dir) - if err != nil { - return definition.Error{Path: dir, Message: fmt.Sprintf("failed to read directory: %v", err)} - } - - for _, entry := range entries { - path := filepath.Join(dir, entry.Name()) - if entry.IsDir() { - // Recursively process subdirectories - if err := processDir(path); err != (definition.Error{}) { - return err - } - } else if filepath.Ext(entry.Name()) == ".json" { - // Process JSON files - compiledSchema, err := compiler.Compile(path) - if err != nil { - return definition.Error{Path: path, Message: fmt.Sprintf("failed to compile JSON schema from file %s: %v", entry.Name(), err)} - } - - // Use relative path from schemaDir to avoid absolute paths and make schema keys domain/version specific. - relativePath, err := filepath.Rel(schemaDir, path) - if err != nil { - return definition.Error{Path: path, Message: fmt.Sprintf("failed to get relative path for file %s: %v", entry.Name(), err)} - } - // Split the relative path to get domain, version, and schema. - parts := strings.Split(relativePath, string(os.PathSeparator)) - - // Ensure that the file path has at least 3 parts: domain, version, and schema file. - if len(parts) < 3 { - return definition.Error{Path: relativePath, Message: "invalid schema file structure, expected domain/version/schema.json"} - } - - // Extract domain, version, and schema filename from the parts. - // Validate that the extracted parts are non-empty - 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 definition.Error{Path: relativePath, Message: "invalid schema file structure, one or more components are empty"} - } - - // 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. - v.SchemaCache[uniqueKey] = compiledSchema - // Store the corresponding validator in the validatorCache using the same unique key. - validatorCache[uniqueKey] = &Validator{schema: compiledSchema} - } - } - return definition.Error{} - } - - // Start processing from the root schema directory - if err := processDir(schemaDir); err != (definition.Error{}) { - return nil, definition.Error{Path: schemaDir, Message: fmt.Sprintf("failed to read schema directory: %v", err)} - } - - return validatorCache, definition.Error{} -} diff --git a/shared/plugin/plugin.yaml b/shared/plugin/plugin.yaml deleted file mode 100644 index 291b0ca..0000000 --- a/shared/plugin/plugin.yaml +++ /dev/null @@ -1,6 +0,0 @@ -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/test.go b/test.go index 50932a3..5d9c813 100644 --- a/test.go +++ b/test.go @@ -1,77 +1,78 @@ package main import ( - "beckn-onix/shared/plugin" - "beckn-onix/shared/plugin/definition" "context" - "encoding/json" "fmt" - "io/ioutil" + "io" "log" "net/http" "net/url" + "strings" + + "github.com/beckn/beckn-onix/pkg/plugin/definition" + + "github.com/beckn/beckn-onix/pkg/plugin" ) var ( - manager *plugin.Manager - validators map[string]definition.Validator + manager *plugin.Manager ) // Payload represents the structure of the payload with context information. -type Payload struct { - Context struct { - 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"` -} +// type Payload struct { +// Context struct { +// 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() { var err error - // Load the configuration - config, err := plugin.LoadConfig("shared/plugin/plugin.yaml") + // Load the configuration. + config, err := plugin.LoadConfig("pkg/plugin/plugin.yaml") if err != nil { log.Fatalf("Failed to load plugins configuration: %v", err) } - // Initialize the plugin manager + // 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, defErr := manager.Validators(context.Background()) - if defErr != (definition.Error{}) { + // Get the validator. + validator, _, defErr := manager.Validator(context.Background()) + if defErr != nil { log.Fatalf("Failed to get validators: %v", defErr) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - validateHandler(w, r, validators) + validateHandler(w, r, validator) }) fmt.Println("Starting server on port 8084...") err = http.ListenAndServe(":8084", nil) @@ -80,13 +81,13 @@ func main() { } } -func validateHandler(w http.ResponseWriter, r *http.Request, validators map[string]definition.Validator) { +func validateHandler(w http.ResponseWriter, r *http.Request, validators definition.SchemaValidator) { if r.Method != http.MethodPost { http.Error(w, "Invalid request method", http.StatusMethodNotAllowed) return } - // Extract endpoint from request URL + // Extract endpoint from request URL. requestURL := r.RequestURI u, err := url.ParseRequestURI(requestURL) if err != nil { @@ -94,40 +95,43 @@ func validateHandler(w http.ResponseWriter, r *http.Request, validators map[stri return } - payloadData, err := ioutil.ReadAll(r.Body) + payloadData, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read payload data", http.StatusInternalServerError) return } - var payload Payload - err = json.Unmarshal(payloadData, &payload) - if err != nil { - log.Printf("Failed to parse JSON payload: %v", err) - http.Error(w, fmt.Sprintf("Failed to parse JSON payload: %v", err), http.StatusBadRequest) - return - } - - // 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 { - http.Error(w, fmt.Sprintf("Validator not found for %s", schemaFileName), http.StatusNotFound) - return - } ctx := context.Background() - valid, validationErr := validator.Validate(ctx, u, payloadData) - if validationErr != (definition.Error{}) { - http.Error(w, fmt.Sprintf("Document validation failed: %v", validationErr), http.StatusBadRequest) - } else if !valid { - http.Error(w, "Document validation failed", http.StatusBadRequest) + // validationErr := validators.Validate(ctx, u, payloadData) + // if validationErr != (definition.SchemaValError{}) { + // http.Error(w, fmt.Sprintf("Document validation failed: %v", validationErr), http.StatusBadRequest) + // } else if !valid { + // http.Error(w, "Document validation failed", http.StatusBadRequest) + // } else { + // w.WriteHeader(http.StatusOK) + // if _, err := w.Write([]byte("Document validation succeeded!")); err != nil { + // log.Fatalf("Failed to write response: %v", err) + // } + // } + validationErr := validators.Validate(ctx, u, payloadData) + if validationErr != nil { + // Check if the error is of type SchemaValidationErr + if schemaErr, ok := validationErr.(*definition.SchemaValidationErr); ok { + // Handle schema validation errors + var errorMessages []string + for _, err := range schemaErr.Errors { + errorMessages = append(errorMessages, fmt.Sprintf("Path: %s, Message: %s", err.Path, err.Message)) + } + errorMessage := fmt.Sprintf("Schema validation failed: %s", strings.Join(errorMessages, "; ")) + http.Error(w, errorMessage, http.StatusBadRequest) + } else { + // Handle other types of errors + http.Error(w, fmt.Sprintf("Schema validation failed: %v", validationErr), http.StatusBadRequest) + } } else { w.WriteHeader(http.StatusOK) - w.Write([]byte("Document validation succeeded!")) + if _, err := w.Write([]byte("Schema validation succeeded!")); err != nil { + log.Fatalf("Failed to write response: %v", err) + } } }