diff --git a/shared/plugin/definition/validator.go b/shared/plugin/definition/validator.go index 776029f..21a1bf8 100644 --- a/shared/plugin/definition/validator.go +++ b/shared/plugin/definition/validator.go @@ -5,12 +5,18 @@ import ( "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) + 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) + 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 index 5b01f75..ebd63b3 100644 Binary files a/shared/plugin/implementations/validator.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 index 0b3b930..b02f214 100644 --- a/shared/plugin/implementations/validator/cmd/plugin.go +++ b/shared/plugin/implementations/validator/cmd/plugin.go @@ -11,11 +11,11 @@ import ( 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) { +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 != nil { - return nil, err + if err != (definition.Error{}) { + return nil, definition.Error{Path: "", Message: err.Message} } // Convert the map to the expected type @@ -24,8 +24,8 @@ func (vp ValidatorProvider) New(ctx context.Context, config map[string]string) ( result[key] = val } - return result, nil + return result, definition.Error{} } // Provider is the exported symbol that the plugin manager will look for. -var Provider definition.ValidatorProvider = ValidatorProvider{} +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 index fc73ff2..d818f47 100644 --- a/shared/plugin/implementations/validator/cmd/plugin_test.go +++ b/shared/plugin/implementations/validator/cmd/plugin_test.go @@ -12,8 +12,8 @@ import ( // 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 +func (m *MockValidator) Validate(ctx context.Context, u *url.URL, data []byte) (bool, definition.Error) { + return true, (definition.Error{}) } // Mock New function for testing @@ -70,7 +70,7 @@ func TestValidatorProvider_New(t *testing.T) { // Check for expected error if tt.expectedError != "" { - if err == nil || err.Error() != tt.expectedError { + if err == (definition.Error{}) || err.Message != tt.expectedError { t.Errorf("expected error %q, got %v", tt.expectedError, err) } return diff --git a/shared/plugin/implementations/validator/validator.go b/shared/plugin/implementations/validator/validator.go index 35ab969..57cf4bb 100644 --- a/shared/plugin/implementations/validator/validator.go +++ b/shared/plugin/implementations/validator/validator.go @@ -10,27 +10,11 @@ import ( "path/filepath" "strings" + "beckn-onix/shared/plugin/definition" + "github.com/santhosh-tekuri/jsonschema/v6" ) -// Validator implements the Validator interface. -type Validator struct { - config map[string]string - schema *jsonschema.Schema - SchemaCache map[string]*jsonschema.Schema -} - -// 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 { - return nil, fmt.Errorf("failed to initialise validators: %v", err) - } - return validators, nil -} - // Payload represents the structure of the data payload with context information. type Payload struct { Context struct { @@ -39,15 +23,30 @@ type Payload struct { } `json:"context"` } -// ValidatorProvider provides instances of Validator. -type ValidatorProvider struct{} +// 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, error) { +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, fmt.Errorf("failed to parse JSON payload: %v", err) + 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 @@ -60,24 +59,26 @@ func (v *Validator) Validate(ctx context.Context, url url.URL, payload []byte) ( 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 + 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, fmt.Errorf("Validation failed: %v", err) + return false, definition.Error{Path: "", Message: fmt.Sprintf("Validation failed: %v", err)} } - return true, nil + 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]*Validator, error) { +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) @@ -87,51 +88,51 @@ func (v *Validator) Initialise() (map[string]*Validator, error) { info, err := os.Stat(schemaDir) if err != nil { if os.IsNotExist(err) { - return nil, fmt.Errorf("schema directory does not exist: %s", schemaDir) + return nil, definition.Error{Path: schemaDir, Message: "schema directory does not exist"} } - return nil, fmt.Errorf("failed to access schema directory: %v", err) + return nil, definition.Error{Path: schemaDir, Message: fmt.Sprintf("failed to access schema directory: %v", err)} } if !info.IsDir() { - return nil, fmt.Errorf("provided schema path is not a directory: %s", schemaDir) + 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]*Validator) + validatorCache := make(map[string]definition.Validator) compiler := jsonschema.NewCompiler() // Helper function to process directories recursively - var processDir func(dir string) error - processDir = func(dir string) error { + var processDir func(dir string) definition.Error + processDir = func(dir string) definition.Error { entries, err := os.ReadDir(dir) if err != nil { - return fmt.Errorf("failed to read directory: %v", err) + 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 != nil { + 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 fmt.Errorf("failed to compile JSON schema from file %s: %v", entry.Name(), err) + 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 fmt.Errorf("failed to get relative path for file %s: %v", entry.Name(), err) + 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 fmt.Errorf("invalid schema file structure, expected domain/version/schema.json but got: %s", relativePath) + return definition.Error{Path: relativePath, Message: "invalid schema file structure, expected domain/version/schema.json"} } // Extract domain, version, and schema filename from the parts. @@ -142,7 +143,7 @@ func (v *Validator) Initialise() (map[string]*Validator, error) { 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) + 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). @@ -153,13 +154,13 @@ func (v *Validator) Initialise() (map[string]*Validator, error) { validatorCache[uniqueKey] = &Validator{schema: compiledSchema} } } - return nil + return definition.Error{} } // Start processing from the root schema directory - if err := processDir(schemaDir); err != nil { - return nil, fmt.Errorf("failed to read schema directory: %v", err) + 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, nil + return validatorCache, definition.Error{} } diff --git a/shared/plugin/implementations/validator/validator_test.go b/shared/plugin/implementations/validator/validator_test.go index 9ae3c3a..3a95761 100644 --- a/shared/plugin/implementations/validator/validator_test.go +++ b/shared/plugin/implementations/validator/validator_test.go @@ -1,7 +1,9 @@ package validator import ( + "beckn-onix/shared/plugin/definition" "context" + "fmt" "net/url" "os" "path/filepath" @@ -66,15 +68,15 @@ func TestValidator_Validate(t *testing.T) { config := map[string]string{"schema_dir": schemaDir} v, err := New(context.Background(), config) - if err != nil { + if err != (definition.Error{}) { 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 != "") { + 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 != "") { t.Errorf("Error: Validate() returned error = %v, expected error = %v", err, tt.wantErr) return } @@ -97,7 +99,8 @@ func TestValidator_Initialise(t *testing.T) { name: "Schema directory does not exist", setupFunc: func(schemaDir string) error { // Do not create the schema directory - return nil + return fmt.Errorf("schema directory does not exist: %s", schemaDir) + }, wantErr: "schema directory does not exist", }, @@ -210,9 +213,9 @@ func TestValidator_Initialise(t *testing.T) { v := &Validator{config: config} _, err := v.Initialise() - if (err != nil && !strings.Contains(err.Error(), tt.wantErr)) || (err == nil && tt.wantErr != "") { + if (err != (definition.Error{}) && !strings.Contains(err.Message, tt.wantErr)) || (err == (definition.Error{}) && tt.wantErr != "") { t.Errorf("Error: Initialise() returned error = %v, expected error = %v", err, tt.wantErr) - } else if err == nil { + } 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) @@ -279,9 +282,9 @@ func TestValidator_New(t *testing.T) { } _, err := New(context.Background(), tt.config) - if (err != nil && !strings.Contains(err.Error(), tt.wantErr)) || (err == nil && tt.wantErr != "") { + if (err != (definition.Error{}) && !strings.Contains(err.Message, tt.wantErr)) || (err == (definition.Error{}) && tt.wantErr != "") { t.Errorf("Error: New() returned error = %v, expected error = %v", err, tt.wantErr) - } else if err == nil { + } 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/shared/plugin/manager.go index 8b70b54..c257826 100644 --- a/shared/plugin/manager.go +++ b/shared/plugin/manager.go @@ -54,11 +54,11 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { } // Initialize validator - validatorMap, err := vp.New(ctx, map[string]string{ + validatorMap, defErr := 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) + if defErr != (definition.Error{}) { + return nil, fmt.Errorf("failed to initialize validator: %v", defErr) } // Initialize the validators map @@ -66,7 +66,6 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { for key, validator := range validatorMap { validators[key] = validator } - fmt.Println("validators : ", validators) return &Manager{vp: vp, validators: validators, cfg: cfg}, nil } @@ -103,20 +102,21 @@ func pluginPath(path, id string) string { } // Validators retrieves the validation plugin instances. -func (m *Manager) Validators(ctx context.Context) (map[string]definition.Validator, error) { +func (m *Manager) Validators(ctx context.Context) (map[string]definition.Validator, definition.Error) { if m.vp == nil { - return nil, fmt.Errorf("validator plugin provider not loaded") - } + return nil, definition.Error{Message: "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) + if err != (definition.Error{}) { + + return nil, definition.Error{Message: fmt.Sprintf("failed to initialize validator: %v", err)} } - return m.validators, nil + return m.validators, definition.Error{} } // LoadConfig loads the configuration from a YAML file. diff --git a/test.go b/test.go index c3d8cb3..50932a3 100644 --- a/test.go +++ b/test.go @@ -65,9 +65,9 @@ func main() { } // Get the validators map - validators, err := manager.Validators(context.Background()) - if err != nil { - log.Fatalf("Failed to get validators: %v", err) + validators, defErr := manager.Validators(context.Background()) + if defErr != (definition.Error{}) { + log.Fatalf("Failed to get validators: %v", defErr) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { @@ -88,7 +88,7 @@ func validateHandler(w http.ResponseWriter, r *http.Request, validators map[stri // Extract endpoint from request URL requestURL := r.RequestURI - u, err := url.Parse(requestURL) + u, err := url.ParseRequestURI(requestURL) if err != nil { http.Error(w, "Failed to parse request URL", http.StatusBadRequest) return @@ -99,8 +99,6 @@ func validateHandler(w http.ResponseWriter, r *http.Request, validators map[stri http.Error(w, "Failed to read payload data", http.StatusInternalServerError) return } - fmt.Println("printing payload data", string(payloadData)) - var payload Payload err = json.Unmarshal(payloadData, &payload) if err != nil { @@ -123,9 +121,9 @@ func validateHandler(w http.ResponseWriter, r *http.Request, validators map[stri } 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) + 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) } else {