From ec558558c5ecc33b26b4bc7a1a1fa1f234c9b294 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Tue, 25 Mar 2025 21:06:34 +0530 Subject: [PATCH 01/15] added updated code for core wiring 1. Removed tracing 2. Skipped Registration --- cmd/adapter/main.go | 175 ++ config/bap.yaml | 69 + config/bapCallerRouting-config.yaml | 3 + config/bpp-local.yaml | 63 + config/bpp.yaml | 63 + config/bppCallerRouting-config.yaml | 3 + config/bppRecieverRouting-config.yaml | 3 + config/byuerApp-config.yaml | 2 + config/onix/bapTxnCaller-routing.yaml | 4 + config/onix/bapTxnReciever-routing.yaml | 4 + config/onix/bppTxnCaller-routing.yaml | 4 + config/onix/bppTxnReciever-routing.yaml | 4 + config/onix/onix-adapter.yaml | 231 ++ config/onix/plugin.yaml | 8 + config/onix/schemas/core/v1.1.0/Cancel.json | 32 + config/onix/schemas/core/v1.1.0/Confirm.json | 43 + config/onix/schemas/core/v1.1.0/Init.json | 43 + config/onix/schemas/core/v1.1.0/OnCancel.json | 43 + .../onix/schemas/core/v1.1.0/OnConfirm.json | 43 + config/onix/schemas/core/v1.1.0/OnInit.json | 43 + config/onix/schemas/core/v1.1.0/OnRating.json | 46 + config/onix/schemas/core/v1.1.0/OnSearch.json | 43 + config/onix/schemas/core/v1.1.0/OnSelect.json | 40 + config/onix/schemas/core/v1.1.0/OnStatus.json | 43 + .../onix/schemas/core/v1.1.0/OnSupport.json | 40 + config/onix/schemas/core/v1.1.0/OnTrack.json | 43 + config/onix/schemas/core/v1.1.0/OnUpdate.json | 43 + config/onix/schemas/core/v1.1.0/Rating.json | 42 + config/onix/schemas/core/v1.1.0/Response.json | 7 + config/onix/schemas/core/v1.1.0/Status.json | 43 + config/onix/schemas/core/v1.1.0/Support.json | 39 + config/onix/schemas/core/v1.1.0/Track.json | 47 + config/onix/schemas/core/v1.1.0/Update.json | 53 + .../onix/schemas/core/v1.1.0/definitions.json | 2459 +++++++++++++++++ .../onix/schemas/core/v1.1.0/on_cancel.json | 46 + .../onix/schemas/core/v1.1.0/on_confirm.json | 46 + config/onix/schemas/core/v1.1.0/on_init.json | 46 + .../onix/schemas/core/v1.1.0/on_rating.json | 47 + .../onix/schemas/core/v1.1.0/on_search.json | 46 + .../onix/schemas/core/v1.1.0/on_select.json | 43 + .../onix/schemas/core/v1.1.0/on_status.json | 46 + .../onix/schemas/core/v1.1.0/on_support.json | 42 + config/onix/schemas/core/v1.1.0/on_track.json | 46 + .../onix/schemas/core/v1.1.0/on_update.json | 46 + config/onix/schemas/core/v1.1.0/search.json | 26 + config/onix/schemas/core/v1.1.0/select.json | 43 + .../schemas/ondc_trv10/v2.0.0/cancel.json | 40 + .../schemas/ondc_trv10/v2.0.0/confirm.json | 463 ++++ .../onix/schemas/ondc_trv10/v2.0.0/init.json | 550 ++++ .../schemas/ondc_trv10/v2.0.0/on_cancel.json | 13 + .../schemas/ondc_trv10/v2.0.0/on_confirm.json | 314 +++ .../schemas/ondc_trv10/v2.0.0/on_init.json | 317 +++ .../schemas/ondc_trv10/v2.0.0/on_rating.json | 13 + .../schemas/ondc_trv10/v2.0.0/on_search.json | 644 +++++ .../schemas/ondc_trv10/v2.0.0/on_select.json | 230 ++ .../schemas/ondc_trv10/v2.0.0/on_status.json | 13 + .../schemas/ondc_trv10/v2.0.0/on_support.json | 13 + .../schemas/ondc_trv10/v2.0.0/on_track.json | 13 + .../schemas/ondc_trv10/v2.0.0/on_update.json | 13 + .../schemas/ondc_trv10/v2.0.0/rating.json | 13 + .../schemas/ondc_trv10/v2.0.0/search.json | 146 + .../schemas/ondc_trv10/v2.0.0/select.json | 16 + .../schemas/ondc_trv10/v2.0.0/status.json | 26 + .../schemas/ondc_trv10/v2.0.0/support.json | 13 + .../onix/schemas/ondc_trv10/v2.0.0/track.json | 13 + .../schemas/ondc_trv10/v2.0.0/update.json | 35 + config/sellerData.yaml | 69 + core/module/client/registery.go | 101 + core/module/handler/config.go | 117 + core/module/handler/stdHandler.go | 264 ++ core/module/handler/step.go | 181 ++ core/module/module.go | 84 + go.mod | 49 +- go.sum | 177 +- pkg/log/log.go | 322 +++ pkg/model/error.go | 128 + pkg/model/model.go | 132 + pkg/plugin/config.go | 21 + pkg/plugin/definition/cache.go | 27 + pkg/plugin/definition/keymanager.go | 35 + pkg/plugin/definition/middleware.go | 10 + pkg/plugin/definition/router.go | 16 + pkg/plugin/definition/schemaValidator.go | 16 + pkg/plugin/definition/step.go | 15 + pkg/plugin/manager.go | 421 ++- pkg/response/response.go | 210 +- pkg/response/response_test.go | 303 -- 87 files changed, 9279 insertions(+), 711 deletions(-) create mode 100644 cmd/adapter/main.go create mode 100644 config/bap.yaml create mode 100644 config/bapCallerRouting-config.yaml create mode 100644 config/bpp-local.yaml create mode 100644 config/bpp.yaml create mode 100644 config/bppCallerRouting-config.yaml create mode 100644 config/bppRecieverRouting-config.yaml create mode 100644 config/byuerApp-config.yaml create mode 100644 config/onix/bapTxnCaller-routing.yaml create mode 100644 config/onix/bapTxnReciever-routing.yaml create mode 100644 config/onix/bppTxnCaller-routing.yaml create mode 100644 config/onix/bppTxnReciever-routing.yaml create mode 100644 config/onix/onix-adapter.yaml create mode 100644 config/onix/plugin.yaml create mode 100644 config/onix/schemas/core/v1.1.0/Cancel.json create mode 100644 config/onix/schemas/core/v1.1.0/Confirm.json create mode 100644 config/onix/schemas/core/v1.1.0/Init.json create mode 100644 config/onix/schemas/core/v1.1.0/OnCancel.json create mode 100644 config/onix/schemas/core/v1.1.0/OnConfirm.json create mode 100644 config/onix/schemas/core/v1.1.0/OnInit.json create mode 100644 config/onix/schemas/core/v1.1.0/OnRating.json create mode 100644 config/onix/schemas/core/v1.1.0/OnSearch.json create mode 100644 config/onix/schemas/core/v1.1.0/OnSelect.json create mode 100644 config/onix/schemas/core/v1.1.0/OnStatus.json create mode 100644 config/onix/schemas/core/v1.1.0/OnSupport.json create mode 100644 config/onix/schemas/core/v1.1.0/OnTrack.json create mode 100644 config/onix/schemas/core/v1.1.0/OnUpdate.json create mode 100644 config/onix/schemas/core/v1.1.0/Rating.json create mode 100644 config/onix/schemas/core/v1.1.0/Response.json create mode 100644 config/onix/schemas/core/v1.1.0/Status.json create mode 100644 config/onix/schemas/core/v1.1.0/Support.json create mode 100644 config/onix/schemas/core/v1.1.0/Track.json create mode 100644 config/onix/schemas/core/v1.1.0/Update.json create mode 100644 config/onix/schemas/core/v1.1.0/definitions.json create mode 100644 config/onix/schemas/core/v1.1.0/on_cancel.json create mode 100644 config/onix/schemas/core/v1.1.0/on_confirm.json create mode 100644 config/onix/schemas/core/v1.1.0/on_init.json create mode 100644 config/onix/schemas/core/v1.1.0/on_rating.json create mode 100644 config/onix/schemas/core/v1.1.0/on_search.json create mode 100644 config/onix/schemas/core/v1.1.0/on_select.json create mode 100644 config/onix/schemas/core/v1.1.0/on_status.json create mode 100644 config/onix/schemas/core/v1.1.0/on_support.json create mode 100644 config/onix/schemas/core/v1.1.0/on_track.json create mode 100644 config/onix/schemas/core/v1.1.0/on_update.json create mode 100644 config/onix/schemas/core/v1.1.0/search.json create mode 100644 config/onix/schemas/core/v1.1.0/select.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/cancel.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/confirm.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/init.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_init.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_search.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_select.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_status.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_support.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_track.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_update.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/rating.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/search.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/select.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/status.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/support.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/track.json create mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/update.json create mode 100644 config/sellerData.yaml create mode 100644 core/module/client/registery.go create mode 100644 core/module/handler/config.go create mode 100644 core/module/handler/stdHandler.go create mode 100644 core/module/handler/step.go create mode 100644 core/module/module.go create mode 100644 pkg/log/log.go create mode 100644 pkg/model/error.go create mode 100644 pkg/model/model.go create mode 100644 pkg/plugin/config.go create mode 100644 pkg/plugin/definition/cache.go create mode 100644 pkg/plugin/definition/keymanager.go create mode 100644 pkg/plugin/definition/middleware.go create mode 100644 pkg/plugin/definition/router.go create mode 100644 pkg/plugin/definition/schemaValidator.go create mode 100644 pkg/plugin/definition/step.go delete mode 100644 pkg/response/response_test.go diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go new file mode 100644 index 0000000..d65e616 --- /dev/null +++ b/cmd/adapter/main.go @@ -0,0 +1,175 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net" + "net/http" + "os" + "strings" + "sync" + "time" + + "gopkg.in/yaml.v2" + + "github.com/beckn/beckn-onix/core/module" + "github.com/beckn/beckn-onix/core/module/handler" + "github.com/beckn/beckn-onix/pkg/log" + "github.com/beckn/beckn-onix/pkg/plugin" +) + +// config struct holds all configurations. +type config struct { + AppName string `yaml:"appName"` + Log log.Config `yaml:"log"` + PluginManager *plugin.ManagerConfig `yaml:"pluginManager"` + Modules []module.Config `yaml:"modules"` + HTTP httpConfig `yaml:"http"` // Nest http config +} + +type httpConfig struct { + Port string `yaml:"port"` + Timeout timeoutConfig `yaml:"timeout"` +} + +type timeoutConfig struct { + Read time.Duration `yaml:"read"` + Write time.Duration `yaml:"write"` + Idle time.Duration `yaml:"idle"` +} + +var configPath string + +func main() { + // Define and parse command-line flags. + flag.StringVar(&configPath, "config", "../../config/clientSideHandler-config.yaml", "Path to the configuration file") + flag.Parse() + + // Use custom log for initial setup messages. + log.Infof(context.Background(), "Starting application with config: %s", configPath) + + // Run the application within a context. + if err := run(context.Background(), configPath); err != nil { + log.Fatalf(context.Background(), err, "Application failed: %v", err) + } + log.Infof(context.Background(), "Application finished") +} + +// initConfig loads and validates the configuration. +func initConfig(ctx context.Context, path string) (*config, error) { + // Open the configuration file. + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("could not open config file: %w", err) + } + defer file.Close() + + // Decode the YAML configuration. + var cfg config + if err := yaml.NewDecoder(file).Decode(&cfg); err != nil { + return nil, fmt.Errorf("could not decode config: %w", err) + } + log.Debugf(ctx, "Read config: %#v", cfg) + // Validate the configuration. + if err := validateConfig(&cfg); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + return &cfg, nil +} + +// validateConfig validates the configuration. +func validateConfig(cfg *config) error { + if strings.TrimSpace(cfg.AppName) == "" { + return fmt.Errorf("missing app name") + } + if strings.TrimSpace(cfg.HTTP.Port) == "" { + return fmt.Errorf("missing port") + } + return nil +} + +// newServer creates and initializes the HTTP server. +func newServer(ctx context.Context, mgr handler.PluginManager, cfg *config) (http.Handler, error) { + mux := http.NewServeMux() + err := module.Register(ctx, cfg.Modules, mux, mgr) + if err != nil { + return nil, fmt.Errorf("failed to register modules: %w", err) + } + return mux, nil +} + +// run encapsulates the application logic. +func run(ctx context.Context, configPath string) error { + closers := []func(){} + // Initialize configuration and logger. + cfg, err := initConfig(ctx, configPath) + if err != nil { + return fmt.Errorf("failed to initialize config: %w", err) + } + log.Infof(ctx, "Initializing logger with config: %+v", cfg.Log) + log.InitLogger(cfg.Log) + + // Initialize plugin manager. + log.Infof(ctx, "Initializing plugin manager") + mgr, closer, err := plugin.NewManager(ctx, cfg.PluginManager) + if err != nil { + return fmt.Errorf("failed to create plugin manager: %w", err) + } + closers = append(closers, closer) + log.Debug(ctx, "Plugin manager loaded.") + + // Initialize HTTP server. + log.Infof(ctx, "Initializing HTTP server") + srv, err := newServer(ctx, mgr, cfg) + if err != nil { + return fmt.Errorf("failed to initialize server: %w", err) + } + + // Configure HTTP server. + httpServer := &http.Server{ + Addr: net.JoinHostPort("", cfg.HTTP.Port), + Handler: srv, + ReadTimeout: cfg.HTTP.Timeout.Read * time.Second, // Use timeouts from config + WriteTimeout: cfg.HTTP.Timeout.Write * time.Second, + IdleTimeout: cfg.HTTP.Timeout.Idle * time.Second, + } + + // Start HTTP server. + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + log.Infof(ctx, "Server listening on %s", httpServer.Addr) + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Errorf(ctx, fmt.Errorf("http server ListenAndServe: %w", err), "error listening and serving") + } + }() + + // Handle shutdown. + shutdown(ctx, httpServer, &wg, closers) + wg.Wait() + log.Infof(ctx, "Server shutdown complete") + return nil +} + +// shutdown handles server shutdown. +func shutdown(ctx context.Context, httpServer *http.Server, wg *sync.WaitGroup, closers []func()) { + wg.Add(1) + go func() { + defer wg.Done() + <-ctx.Done() + log.Infof(ctx, "Shutting down server...") + shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := httpServer.Shutdown(shutdownCtx); err != nil { + log.Errorf(ctx, fmt.Errorf("http server Shutdown: %w", err), "error shutting down http server") + } + + // Call all closer functions. + for _, closer := range closers { + closer() + } + }() +} diff --git a/config/bap.yaml b/config/bap.yaml new file mode 100644 index 0000000..25c67b2 --- /dev/null +++ b/config/bap.yaml @@ -0,0 +1,69 @@ +appName: "bapAdapter" +log: + level: debug + destinations: + - type: stdout + context_keys: + - transaction_id + - message_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +plugin: + root: /app/plugins + pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip + plugins: + - publisher Src version raw comp zip + - nopschemavalidator + - router + - nopsigner + - nopsignvalidator + - reqpreprocessor + - gcpAuthMdw +modules: + - name: reciever + type: transaction + path: /reciever + targetType: msgQ + plugin: + schemaValidator: + id: nopschemavalidator + signValidator: + id: nopsignvalidator + publisher: + id: publisher + config: + project: ondc-seller-dev + topic: bapNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bapRecieverRouting-config.yaml + preProcessors: + - id: reqpreprocessor + steps: + steps: + - addRoute + signValidate + -addRout + customValidate + - name: transactionCaller + path: /caller + targetType: "http" + plugin: + signer: + id: nopsigner + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bapCallerRouting-config.yaml + preProcessors: + - id: reqpreprocessor + # postProcessors: + # - id: gcpAuthMdw + # config: + # audience: https://bpp-adapter-903496459467.asia-southeast1.run.app + # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bapCallerRouting-config.yaml b/config/bapCallerRouting-config.yaml new file mode 100644 index 0000000..a6702ea --- /dev/null +++ b/config/bapCallerRouting-config.yaml @@ -0,0 +1,3 @@ +routes: + - action: search + target: https://bpp-adapter-903496459467.asia-southeast1.run.app/reciever \ No newline at end of file diff --git a/config/bpp-local.yaml b/config/bpp-local.yaml new file mode 100644 index 0000000..5af87f4 --- /dev/null +++ b/config/bpp-local.yaml @@ -0,0 +1,63 @@ +appName: "bppClientService" +log: + level: debug + destinations: + - type: stdout + context_keys: + - transaction_id + - message_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +plugin: + root: extracted/plugins + pluginZipPath: plugins_bundle.zip + plugins: + - publisher + - nopschemavalidator + - router + - nopsigner + - nopsignvalidator + - reqpreprocessor + - gcpAuthMdw +module: + modules: + - name: transactionReciever + path: /reciever + targetType: msgQ + plugin: + schemaValidator: + id: nopschemavalidator + signValidator: + id: nopsignValidator + publisher: + id: publisher + config: + project: ondc-seller-dev + topic: clientSideTopic + Router: + id: router + config: + routingConfigPath: configs/bppRecieverRouting-config.yaml + preProcessors: + - id: reqpreprocessor + - name: transactionCaller + path: /caller + targetType: "http" + plugin: + signer: + id: nopsigner + Router: + id: router + config: + routingConfigPath: configs/bppCallerRouting-config.yaml + preProcessors: + - id: reqpreprocessor + postProcessors: + - id: gcpAuthMdw + config: + audience: "target" + diff --git a/config/bpp.yaml b/config/bpp.yaml new file mode 100644 index 0000000..a4d9a65 --- /dev/null +++ b/config/bpp.yaml @@ -0,0 +1,63 @@ +appName: "bppClientService" +log: + level: debug + destinations: + - type: stdout + context_keys: + - transaction_id + - message_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +plugin: + root: /app/plugins + pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip + plugins: + - publisher + - nopschemavalidator + - router + - nopsigner + - nopsignvalidator + - reqpreprocessor + - gcpAuthMdw +module: + modules: + - name: transactionReciever + path: /reciever + targetType: msgQ + plugin: + schemaValidator: + id: nopschemavalidator + signValidator: + id: nopsignvalidator + publisher: + id: publisher + config: + project: ondc-seller-dev + topic: bppNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bppRecieverRouting-config.yaml + preProcessors: + - id: reqpreprocessor + - name: transactionCaller + path: /caller + targetType: "http" + plugin: + signer: + id: nopsigner + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bppCallerRouting-config.yaml + preProcessors: + - id: reqpreprocessor + # postProcessors: + # - id: gcpAuthMdw + # config: + # audience: https://bap-adapter-903496459467.asia-southeast1.run.app + # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bppCallerRouting-config.yaml b/config/bppCallerRouting-config.yaml new file mode 100644 index 0000000..9aa5e39 --- /dev/null +++ b/config/bppCallerRouting-config.yaml @@ -0,0 +1,3 @@ +routes: + - action: on_search + target: targeturl \ No newline at end of file diff --git a/config/bppRecieverRouting-config.yaml b/config/bppRecieverRouting-config.yaml new file mode 100644 index 0000000..15fdb33 --- /dev/null +++ b/config/bppRecieverRouting-config.yaml @@ -0,0 +1,3 @@ +routes: + - action: search + target: https://sellerapp-903496459467.asia-southeast1.run.app \ No newline at end of file diff --git a/config/byuerApp-config.yaml b/config/byuerApp-config.yaml new file mode 100644 index 0000000..a441371 --- /dev/null +++ b/config/byuerApp-config.yaml @@ -0,0 +1,2 @@ +bap_url: "https://bap-csr-903496459467.asia-southeast1.run.app" # Replace with actual Beckn API endpoint +port: "8080" # The port on which the server will run \ No newline at end of file diff --git a/config/onix/bapTxnCaller-routing.yaml b/config/onix/bapTxnCaller-routing.yaml new file mode 100644 index 0000000..3d59ad0 --- /dev/null +++ b/config/onix/bapTxnCaller-routing.yaml @@ -0,0 +1,4 @@ +routes: + - action: search + type: url + target: http://localhost:8080/bpp/reciever/search \ No newline at end of file diff --git a/config/onix/bapTxnReciever-routing.yaml b/config/onix/bapTxnReciever-routing.yaml new file mode 100644 index 0000000..7c4da0d --- /dev/null +++ b/config/onix/bapTxnReciever-routing.yaml @@ -0,0 +1,4 @@ +routes: + - action: on_search + type: publisher + target: bapNetworkReciever \ No newline at end of file diff --git a/config/onix/bppTxnCaller-routing.yaml b/config/onix/bppTxnCaller-routing.yaml new file mode 100644 index 0000000..688b9af --- /dev/null +++ b/config/onix/bppTxnCaller-routing.yaml @@ -0,0 +1,4 @@ +routes: + - action: on_search + type: url + target: http://localhost:8080/bap/reciever/on_search \ No newline at end of file diff --git a/config/onix/bppTxnReciever-routing.yaml b/config/onix/bppTxnReciever-routing.yaml new file mode 100644 index 0000000..e9ba98c --- /dev/null +++ b/config/onix/bppTxnReciever-routing.yaml @@ -0,0 +1,4 @@ +routes: + - action: search + type: publisher + target: bapNetworkReciever \ No newline at end of file diff --git a/config/onix/onix-adapter.yaml b/config/onix/onix-adapter.yaml new file mode 100644 index 0000000..3697136 --- /dev/null +++ b/config/onix/onix-adapter.yaml @@ -0,0 +1,231 @@ +appName: "onix" +log: + level: debug + destinations: + - type: stdout + contextKeys: + - transaction_id + - message_id + - subscriber_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +pluginManager: + root: /app/plugins + remoteRoot: /mnt/gcs/plugins/plugins_bundle.zip +modules: + - name: bapTxnReciever + path: /bap/reciever/ + handler: + type: std + role: bap + trace: + # validateSign: true + # addRoute: true + # validateSchema: true + # reqpreprocessor: true + registryUrl: http://localhost:8080/reg + plugins: + keyManager: + id: secretskeymanager + config: + projectID: trusty-relic-370809 + cache: + id: redis + config: + addr: 10.81.192.4:6379 + # schemaValidator: + # id: schemavalidator + # config: + # schemaDir: /mnt/gcs/configs/schemas + signValidator: + id: signvalidator + publisher: + id: publisher + config: + project: trusty-relic-370809 + topic: bapNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bapTxnReciever-routing.yaml + middleware: + - id: reqpreprocessor + config: + uuidKeys: transaction_id,message_id + role: bap + steps: + - validateSign + - addRoute + # - validateSchema + - name: bapTxnCaller + path: /bap/caller/ + handler: + type: std + registryUrl: http://localhost:8080/reg + role: bap + plugins: + keyManager: + id: secretskeymanager + config: + projectID: trusty-relic-370809 + cache: + id: redis + config: + addr: 10.81.192.4:6379 + # schemaValidator: + # id: schemavalidator + # config: + # schemaDir: /mnt/gcs/configs/schemas + signer: + id: signer + publisher: + id: publisher + config: + project: trusty-relic-370809 + topic: bapNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bapTxnCaller-routing.yaml + middleware: + - id: reqpreprocessor + config: + uuidKeys: transaction_id,message_id + role: bap + steps: + # - validateSchema + - addRoute + - sign + - name: bapSubscribeCaller + path: /bap/subscribe + handler: + type: npSub + role: bap + registryUrl: http://localhost:8080/reg + plugins: + keyManager: + id: secretskeymanager + config: + projectID: trusty-relic-370809 + cache: + id: redis + config: + addr: 10.81.192.4:6379 + - name: bppTxnReciever + path: /bpp/reciever/ + handler: + type: std + role: bpp + subscriberId: bpp1 + registryUrl: http://localhost:8080/reg + plugins: + keyManager: + id: secretskeymanager + config: + projectID: trusty-relic-370809 + cache: + id: redis + config: + addr: 10.81.192.4:6379 + # schemaValidator: + # id: schemavalidator + # config: + # schemaDir: /mnt/gcs/configs/schemas + signValidator: + id: signvalidator + publisher: + id: publisher + config: + project: trusty-relic-370809 + topic: bapNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bppTxnReciever-routing.yaml + middleware: + - id: reqpreprocessor + config: + uuidKeys: transaction_id,message_id + role: bpp + steps: + - validateSign + - addRoute + # - validateSchema + - name: bppTxnCaller + path: /bpp/caller/ + handler: + type: std + role: bpp + registryUrl: http://localhost:8080/reg + plugins: + keyManager: + id: secretskeymanager + config: + projectID: trusty-relic-370809 + cache: + id: redis + config: + addr: 10.81.192.4:6379 + # schemaValidator: + # id: schemavalidator + # config: + # schemaDir: /mnt/gcs/configs/schemas + signer: + id: signer + publisher: + id: publisher + config: + project: trusty-relic-370809 + topic: bapNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bppTxnCaller-routing.yaml + middleware: + - id: reqpreprocessor + config: + uuidKeys: transaction_id,message_id + role: bpp + steps: + # - validateSchema + - addRoute + - sign + - name: bppSubscribeCaller + path: /bpp/subscribe + handler: + type: npSub + role: bpp + registryUrl: http://localhost:8080/reg + plugins: + keyManager: + id: secretskeymanager + config: + projectID: trusty-relic-370809 + cache: + id: redis + config: + addr: 10.81.192.4:6379 + - name: regSubscribeReciever + path: /reg/subscribe + handler: + type: regSub + role: registery + plugins: + cache: + id: redis + config: + addr: "10.81.192.4:6379" + - name: regLookUpReciever + path: /reg/lookUp + handler: + type: lookUp + role: registery + plugins: + cache: + id: redis + config: + addr: "10.81.192.4:6379" diff --git a/config/onix/plugin.yaml b/config/onix/plugin.yaml new file mode 100644 index 0000000..4c2bb72 --- /dev/null +++ b/config/onix/plugin.yaml @@ -0,0 +1,8 @@ +plugins: + - gcpAuthMdw + - nopsigner + - router + - publisher + - reqpreprocessor + - nopschemavalidator + - nopsignvalidator \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Cancel.json b/config/onix/schemas/core/v1.1.0/Cancel.json new file mode 100644 index 0000000..b09e97a --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Cancel.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "cancel", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "cancel" + ] + } + }, + "required": [ + "action" + ] + } + ] + } + + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Confirm.json b/config/onix/schemas/core/v1.1.0/Confirm.json new file mode 100644 index 0000000..d4b4277 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Confirm.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "confirm", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "confirm" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Init.json b/config/onix/schemas/core/v1.1.0/Init.json new file mode 100644 index 0000000..fec48db --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Init.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "init", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "init" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnCancel.json b/config/onix/schemas/core/v1.1.0/OnCancel.json new file mode 100644 index 0000000..4eafef2 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnCancel.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnCancel", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_cancel" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnConfirm.json b/config/onix/schemas/core/v1.1.0/OnConfirm.json new file mode 100644 index 0000000..6043056 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnConfirm.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnConfirm", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_confirm" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnInit.json b/config/onix/schemas/core/v1.1.0/OnInit.json new file mode 100644 index 0000000..be74e86 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnInit.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnInit", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_init" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnRating.json b/config/onix/schemas/core/v1.1.0/OnRating.json new file mode 100644 index 0000000..8864f06 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnRating.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnRating", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_rating" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "feedback_form": { + "description": "A feedback form to allow the user to provide additional information on the rating provided", + "allOf": [ + { + "$ref": "./definitions.json#/$defs/XInput" + } + ] + } + } + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context", + "message" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnSearch.json b/config/onix/schemas/core/v1.1.0/OnSearch.json new file mode 100644 index 0000000..982b26d --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnSearch.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnSearch", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_search" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "catalog": { + "$ref": "./definitions.json#/$defs/Catalog" + } + }, + "required": [ + "catalog" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnSelect.json b/config/onix/schemas/core/v1.1.0/OnSelect.json new file mode 100644 index 0000000..fa86378 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnSelect.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnSelect", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_select" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + } + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnStatus.json b/config/onix/schemas/core/v1.1.0/OnStatus.json new file mode 100644 index 0000000..7453a11 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnStatus.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnStatus", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_status" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnSupport.json b/config/onix/schemas/core/v1.1.0/OnSupport.json new file mode 100644 index 0000000..ed91c42 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnSupport.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnSupport", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_support" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "support": { + "$ref": "./definitions.json#/$defs/Support" + } + } + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnTrack.json b/config/onix/schemas/core/v1.1.0/OnTrack.json new file mode 100644 index 0000000..7d46838 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnTrack.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnTrack", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_track" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "tracking": { + "$ref": "./definitions.json#/$defs/Tracking" + } + }, + "required": [ + "tracking" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnUpdate.json b/config/onix/schemas/core/v1.1.0/OnUpdate.json new file mode 100644 index 0000000..2b2f2e8 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/OnUpdate.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OnUpdate", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "properties": { + "action": { + "enum": [ + "on_update" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Rating.json b/config/onix/schemas/core/v1.1.0/Rating.json new file mode 100644 index 0000000..3006d12 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Rating.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "rating", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "rating" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "ratings": { + "type": "array", + "items": { + "$ref": "./definitions.json#/$defs/Rating" + } + } + } + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Response.json b/config/onix/schemas/core/v1.1.0/Response.json new file mode 100644 index 0000000..cbd7372 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Response.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "Response", + "type": "object", + "properties": {}, + "required": [] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Status.json b/config/onix/schemas/core/v1.1.0/Status.json new file mode 100644 index 0000000..871f693 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Status.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "status", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "status" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "ref_id": { + "$ref": "./definitions.json#/$defs/Order" + }, + "order_id": { + "$ref": "./definitions.json#/$defs/Order" + } + } + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Support.json b/config/onix/schemas/core/v1.1.0/Support.json new file mode 100644 index 0000000..729b81a --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Support.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "support", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "support" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "support": { + "$ref": "./definitions.json#/$defs/Support" + } + } + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Track.json b/config/onix/schemas/core/v1.1.0/Track.json new file mode 100644 index 0000000..ec891b1 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Track.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "track", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "track" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order_id": { + "$ref": "./definitions.json#/$defs/Order" + }, + "callback_url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "order_id" + ] + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Update.json b/config/onix/schemas/core/v1.1.0/Update.json new file mode 100644 index 0000000..6a664fa --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/Update.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "update", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "update" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "update_target": { + "description": "Comma separated values of order objects being updated. For example: ```\"update_target\":\"item,billing,fulfillment\"```", + "type": "string" + }, + "order": { + "description": "Updated order object", + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Order" + } + ] + } + }, + "required": [ + "update_target", + "order" + ] + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/definitions.json b/config/onix/schemas/core/v1.1.0/definitions.json new file mode 100644 index 0000000..ed7eec1 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/definitions.json @@ -0,0 +1,2459 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "definitions.json", + "$defs": { + "Ack": { + "$id": "Ack", + "description": "Describes the acknowledgement sent in response to an API call. If the implementation uses HTTP/S, then Ack must be returned in the same session. Every API call to a BPP must be responded to with an Ack whether the BPP intends to respond with a callback or not. This has one property called `status` that indicates the status of the Acknowledgement.", + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "description": "The status of the acknowledgement. If the request passes the validation criteria of the BPP, then this is set to ACK. If a BPP responds with status = `ACK` to a request, it is required to respond with a callback. If the request fails the validation criteria, then this is set to NACK. Additionally, if a BPP does not intend to respond with a callback even after the request meets the validation criteria, it should set this value to `NACK`.", + "enum": [ + "ACK", + "NACK" + ] + }, + "tags": { + "description": "A list of tags containing any additional information sent along with the Acknowledgement.", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "AddOn": { + "$id": "AddOn", + "description": "Describes an additional item offered as a value-addition to a product or service. This does not exist independently in a catalog and is always associated with an item.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "Provider-defined ID of the add-on", + "type": "string" + }, + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "price": { + "$ref": "definitions.json#/$defs/Price" + }, + "quantity": { + "$ref": "definitions.json#/$defs/ItemQuantity" + } + } + }, + "Address": { + "$id": "Address", + "description": "Describes a postal address.", + "type": "string" + }, + "Agent": { + "$id": "Agent", + "description": "Describes the direct performer, driver or executor that fulfills an order. It is usually a person. But in some rare cases, it could be a non-living entity like a drone, or a bot. Some examples of agents are Doctor in the healthcare sector, a driver in the mobility sector, or a delivery person in the logistics sector. This object can be set at any stage of the order lifecycle. This can be set at the discovery stage when the BPP wants to provide details on the agent fulfilling the order, like in healthcare, where the doctor's name appears during search. This object can also used to search for a particular person that the customer wants fulfilling an order. Sometimes, this object gets instantiated after the order is confirmed, like in the case of on-demand taxis, where the driver is assigned after the user confirms the ride.", + "type": "object", + "additionalProperties": false, + "properties": { + "person": { + "$ref": "definitions.json#/$defs/Person" + }, + "contact": { + "$ref": "definitions.json#/$defs/Contact" + }, + "organization": { + "$ref": "definitions.json#/$defs/Organization" + }, + "rating": { + "$ref": "definitions.json#/$defs/Rating/properties/value" + } + } + }, + "Authorization": { + "$id": "Authorization", + "description": "Describes an authorization mechanism used to start or end the fulfillment of an order. For example, in the mobility sector, the driver may require a one-time password to initiate the ride. In the healthcare sector, a patient may need to provide a password to open a video conference link during a teleconsultation.", + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "description": "Type of authorization mechanism used. The allowed values for this field can be published as part of the network policy.", + "type": "string" + }, + "token": { + "description": "Token used for authorization. This is typically generated at the BPP. The BAP can send this value to the user via any channel that it uses to authenticate the user like SMS, Email, Push notification, or in-app rendering.", + "type": "string" + }, + "valid_from": { + "description": "Timestamp in RFC3339 format from which token is valid", + "type": "string", + "format": "date-time" + }, + "valid_to": { + "description": "Timestamp in RFC3339 format until which token is valid", + "type": "string", + "format": "date-time" + }, + "status": { + "description": "Status of the token", + "type": "string" + } + } + }, + "Billing": { + "$id": "Billing", + "description": "Describes the billing details of an entity.
This has properties like name,organization,address,email,phone,time,tax_number, created_at,updated_at", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Name of the billable entity", + "type": "string" + }, + "organization": { + "description": "Details of the organization being billed.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Organization" + } + ] + }, + "address": { + "description": "The address of the billable entity", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Address" + } + ] + }, + "state": { + "description": "The state where the billable entity resides. This is important for state-level tax calculation", + "allOf": [ + { + "$ref": "definitions.json#/$defs/State" + } + ] + }, + "city": { + "description": "The city where the billable entity resides.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/City" + } + ] + }, + "email": { + "description": "Email address where the bill is sent to", + "type": "string", + "format": "email" + }, + "phone": { + "description": "Phone number of the billable entity", + "type": "string" + }, + "time": { + "description": "Details regarding the billing period", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Time" + } + ] + }, + "tax_id": { + "description": "ID of the billable entity as recognized by the taxation authority", + "type": "string" + } + } + }, + "Cancellation": { + "$id": "Cancellation", + "description": "Describes a cancellation event", + "type": "object", + "additionalProperties": false, + "properties": { + "time": { + "description": "Date-time when the order was cancelled by the buyer", + "type": "string", + "format": "date-time" + }, + "cancelled_by": { + "type": "string", + "enum": [ + "CONSUMER", + "PROVIDER" + ] + }, + "reason": { + "description": "The reason for cancellation", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Option" + } + ] + }, + "additional_description": { + "description": "Any additional information regarding the nature of cancellation", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Descriptor" + } + ] + } + } + }, + "CancellationTerm": { + "$id": "CancellationTerm", + "description": "Describes the cancellation terms of an item or an order. This can be referenced at an item or order level. Item-level cancellation terms can override the terms at the order level.", + "type": "object", + "additionalProperties": false, + "properties": { + "fulfillment_state": { + "description": "The state of fulfillment during which this term is applicable.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/FulfillmentState" + } + ] + }, + "reason_required": { + "description": "Indicates whether a reason is required to cancel the order", + "type": "boolean" + }, + "cancel_by": { + "description": "Information related to the time of cancellation.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Time" + } + ] + }, + "cancellation_fee": { + "$ref": "definitions.json#/$defs/Fee" + }, + "xinput": { + "$ref": "definitions.json#/$defs/XInput" + }, + "external_ref": { + "$ref": "definitions.json#/$defs/MediaFile" + } + } + }, + "Catalog": { + "$id": "Catalog", + "description": "Describes the products or services offered by a BPP. This is typically sent as the response to a search intent from a BAP. The payment terms, offers and terms of fulfillment supported by the BPP can also be included here. The BPP can show hierarchical nature of products/services in its catalog using the parent_category_id in categories. The BPP can also send a ttl (time to live) in the context which is the duration for which a BAP can cache the catalog and use the cached catalog.
This has properties like bbp/descriptor,bbp/categories,bbp/fulfillments,bbp/payments,bbp/offers,bbp/providers and exp
This is used in the following situations.
", + "type": "object", + "additionalProperties": false, + "properties": { + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "fulfillments": { + "description": "Fulfillment modes offered at the BPP level. This is used when a BPP itself offers fulfillments on behalf of the providers it has onboarded.", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Fulfillment" + } + }, + "payments": { + "description": "Payment terms offered by the BPP for all transactions. This can be overriden at the provider level.", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Payment" + } + }, + "offers": { + "description": "Offers at the BPP-level. This is common across all providers onboarded by the BPP.", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Offer" + } + }, + "providers": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Provider" + } + }, + "exp": { + "description": "Timestamp after which catalog will expire", + "type": "string", + "format": "date-time" + }, + "ttl": { + "description": "Duration in seconds after which this catalog will expire", + "type": "string" + } + } + }, + "Category": { + "$id": "Category", + "description": "A label under which a collection of items can be grouped.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "ID of the category", + "type": "string" + }, + "parent_category_id": { + "$ref": "definitions.json#/$defs/Category/properties/id" + }, + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "time": { + "$ref": "definitions.json#/$defs/Time" + }, + "ttl": { + "description": "Time to live for an instance of this schema" + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "Circle": { + "$id": "Circle", + "description": "Describes a circular region of a specified radius centered at a specified GPS coordinate.", + "type": "object", + "additionalProperties": false, + "properties": { + "gps": { + "$ref": "definitions.json#/$defs/Gps" + }, + "radius": { + "$ref": "definitions.json#/$defs/Scalar" + } + } + }, + "City": { + "$id": "City", + "description": "Describes a city", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Name of the city", + "type": "string" + }, + "code": { + "description": "City code", + "type": "string" + } + } + }, + "Contact": { + "$id": "Contact", + "description": "Describes the contact information of an entity", + "type": "object", + "additionalProperties": false, + "properties": { + "phone": { + "type": "string" + }, + "email": { + "type": "string" + }, + "jcard": { + "type": "object", + "additionalProperties": false, + "description": "A Jcard object as per draft-ietf-jcardcal-jcard-03 specification" + } + } + }, + "Context": { + "$id": "Context", + "description": "Every API call in beckn protocol has a context. It provides a high-level overview to the receiver about the nature of the intended transaction. Typically, it is the BAP that sets the transaction context based on the consumer's location and action on their UI. But sometimes, during unsolicited callbacks, the BPP also sets the transaction context but it is usually the same as the context of a previous full-cycle, request-callback interaction between the BAP and the BPP. The context object contains four types of fields.
  1. Demographic information about the transaction using fields like `domain`, `country`, and `region`.
  2. Addressing details like the sending and receiving platform's ID and API URL.
  3. Interoperability information like the protocol version that implemented by the sender and,
  4. Transaction details like the method being called at the receiver's endpoint, the transaction_id that represents an end-to-end user session at the BAP, a message ID to pair requests with callbacks, a timestamp to capture sending times, a ttl to specifiy the validity of the request, and a key to encrypt information if necessary.
This object must be passed in every interaction between a BAP and a BPP. In HTTP/S implementations, it is not necessary to send the context during the synchronous response. However, in asynchronous protocols, the context must be sent during all interactions,", + "type": "object", + "additionalProperties": false, + "properties": { + "domain": { + "description": "Domain code that is relevant to this transaction context", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Domain/properties/code", + "type": "string" + } + ] + }, + "location": { + "description": "The location where the transaction is intended to be fulfilled.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Location" + } + ] + }, + "action": { + "description": "The Beckn protocol method being called by the sender and executed at the receiver.", + "type": "string" + }, + "version": { + "type": "string", + "description": "Version of transaction protocol being used by the sender." + }, + "bap_id": { + "description": "Subscriber ID of the BAP", + "allOf": [ + { + "description": "A globally unique identifier of the platform, Typically it is the fully qualified domain name (FQDN) of the platform.", + "type": "string" + } + ] + }, + "bap_uri": { + "description": "Subscriber URL of the BAP for accepting callbacks from BPPs.", + "allOf": [ + { + "description": "The callback URL of the Subscriber. This should necessarily contain the same domain name as set in `subscriber_id``.", + "type": "string", + "format": "uri" + } + ] + }, + "bpp_id": { + "description": "Subscriber ID of the BPP", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Context/properties/bap_id/allOf/0" + } + ] + }, + "bpp_uri": { + "description": "Subscriber URL of the BPP for accepting calls from BAPs.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Context/properties/bap_uri/allOf/0" + } + ] + }, + "transaction_id": { + "description": "This is a unique value which persists across all API calls from `search` through `confirm`. This is done to indicate an active user session across multiple requests. The BPPs can use this value to push personalized recommendations, and dynamic offerings related to an ongoing transaction despite being unaware of the user active on the BAP.", + "type": "string", + "format": "uuid" + }, + "message_id": { + "description": "This is a unique value which persists during a request / callback cycle. Since beckn protocol APIs are asynchronous, BAPs need a common value to match an incoming callback from a BPP to an earlier call. This value can also be used to ignore duplicate messages coming from the BPP. It is recommended to generate a fresh message_id for every new interaction. When sending unsolicited callbacks, BPPs must generate a new message_id.", + "type": "string", + "format": "uuid" + }, + "timestamp": { + "description": "Time of request generation in RFC3339 format", + "type": "string", + "format": "date-time" + }, + "key": { + "description": "The encryption public key of the sender", + "type": "string" + }, + "ttl": { + "description": "The duration in ISO8601 format after timestamp for which this message holds valid", + "type": "string" + } + } + }, + "Country": { + "$id": "Country", + "description": "Describes a country", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the country" + }, + "code": { + "type": "string", + "description": "Country code as per ISO 3166-1 and ISO 3166-2 format" + } + } + }, + "Credential": { + "$id": "Credential", + "description": "Describes a credential of an entity - Person or Organization", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "default": "VerifiableCredential" + }, + "url": { + "description": "URL of the credential", + "type": "string", + "format": "uri" + } + } + }, + "Customer": { + "$id": "Customer", + "description": "Describes a customer buying/availing a product or a service", + "type": "object", + "additionalProperties": false, + "properties": { + "person": { + "$ref": "definitions.json#/$defs/Person" + }, + "contact": { + "$ref": "definitions.json#/$defs/Contact" + } + } + }, + "DecimalValue": { + "$id": "DecimalValue", + "description": "Describes a numerical value in decimal form", + "type": "string", + "pattern": "[+-]?([0-9]*[.])?[0-9]+" + }, + "Descriptor": { + "$id": "Descriptor", + "description": "Physical description of something.", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "short_desc": { + "type": "string" + }, + "long_desc": { + "type": "string" + }, + "additional_desc": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string" + }, + "content_type": { + "type": "string", + "enum": [ + "text/plain", + "text/html", + "application/json" + ] + } + } + }, + "media": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/MediaFile" + } + }, + "images": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Image" + } + } + } + }, + "Domain": { + "$id": "Domain", + "description": "Described the industry sector or sub-sector. The network policy should contain codes for all the industry sectors supported by the network. Domains can be created in varying levels of granularity. The granularity of a domain can be decided by the participants of the network. Too broad domains will result in irrelevant search broadcast calls to BPPs that don't have services supporting the domain. Too narrow domains will result in a large number of registry entries for each BPP. It is recommended that network facilitators actively collaborate with various working groups and network participants to carefully choose domain codes keeping in mind relevance, performance, and opportunity cost. It is recommended that networks choose broad domains like mobility, logistics, healthcare etc, and progressively granularize them as and when the number of network participants for each domain grows large.", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Name of the domain", + "type": "string" + }, + "code": { + "description": "Standard code representing the domain. The standard is usually published as part of the network policy. Furthermore, the network facilitator should also provide a mechanism to provide the supported domains of a network." + }, + "additional_info": { + "description": "A url that contains addtional information about that domain.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/MediaFile" + } + ] + } + } + }, + "Duration": { + "$id": "Duration", + "description": "Describes duration as per ISO8601 format", + "type": "string" + }, + "Error": { + "$id": "Error", + "description": "Describes an error object that is returned by a BAP, BPP or BG as a response or callback to an action by another network participant. This object is sent when any request received by a network participant is unacceptable. This object can be sent either during Ack or with the callback.", + "type": "object", + "additionalProperties": false, + "properties": { + "code": { + "type": "string", + "description": "Standard error code. For full list of error codes, refer to docs/protocol-drafts/BECKN-005-ERROR-CODES-DRAFT-01.md of this repo\"" + }, + "paths": { + "type": "string", + "description": "Path to json schema generating the error. Used only during json schema validation errors" + }, + "message": { + "type": "string", + "description": "Human readable message describing the error. Used mainly for logging. Not recommended to be shown to the user." + } + } + }, + "Fee": { + "$id": "Fee", + "description": "A fee applied on a particular entity", + "type": "object", + "additionalProperties": false, + "properties": { + "percentage": { + "description": "Percentage of a value", + "allOf": [ + { + "$ref": "definitions.json#/$defs/DecimalValue" + } + ] + }, + "amount": { + "description": "A fixed value", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Price" + } + ] + } + } + }, + "Form": { + "$id": "Form", + "description": "Describes a form", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "The form identifier.", + "type": "string" + }, + "url": { + "description": "The URL from where the form can be fetched. The content fetched from the url must be processed as per the mime_type specified in this object. Once fetched, the rendering platform can choosed to render the form as-is as an embeddable element; or process it further to blend with the theme of the application. In case the interface is non-visual, the the render can process the form data and reproduce it as per the standard specified in the form.", + "type": "string", + "format": "uri" + }, + "data": { + "description": "The form submission data", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mime_type": { + "description": "This field indicates the nature and format of the form received by querying the url. MIME types are defined and standardized in IETF's RFC 6838.", + "type": "string", + "enum": [ + "text/html", + "application/html", + "application/xml" + ] + }, + "resubmit": { + "type": "boolean" + }, + "multiple_sumbissions": { + "type": "boolean" + } + } + }, + "Fulfillment": { + "$id": "Fulfillment", + "description": "Describes how a an order will be rendered/fulfilled to the end-customer", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "Unique reference ID to the fulfillment of an order", + "type": "string" + }, + "type": { + "description": "A code that describes the mode of fulfillment. This is typically set when there are multiple ways an order can be fulfilled. For example, a retail order can be fulfilled either via store pickup or a home delivery. Similarly, a medical consultation can be provided either in-person or via tele-consultation. The network policy must publish standard fulfillment type codes for the different modes of fulfillment.", + "type": "string" + }, + "rateable": { + "description": "Whether the fulfillment can be rated or not", + "type": "boolean" + }, + "rating": { + "description": "The rating value of the fulfullment service.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Rating/properties/value" + } + ] + }, + "state": { + "description": "The current state of fulfillment. The BPP must set this value whenever the state of the order fulfillment changes and fire an unsolicited `on_status` call.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/FulfillmentState" + } + ] + }, + "tracking": { + "type": "boolean", + "description": "Indicates whether the fulfillment allows tracking", + "default": false + }, + "customer": { + "description": "The person that will ultimately receive the order", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Customer" + } + ] + }, + "agent": { + "description": "The agent that is currently handling the fulfillment of the order", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Agent" + } + ] + }, + "contact": { + "$ref": "definitions.json#/$defs/Contact" + }, + "vehicle": { + "$ref": "definitions.json#/$defs/Vehicle" + }, + "stops": { + "description": "The list of logical stops encountered during the fulfillment of an order.", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Stop" + } + }, + "path": { + "description": "The physical path taken by the agent that can be rendered on a map. The allowed format of this property can be set by the network.", + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "FulfillmentState": { + "$id": "FulfillmentState", + "description": "Describes the state of fulfillment", + "type": "object", + "additionalProperties": false, + "properties": { + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "updated_by": { + "type": "string", + "description": "ID of entity which changed the state" + } + } + }, + "Gps": { + "$id": "Gps", + "description": "Describes a GPS coordinate", + "type": "string", + "pattern": "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?),\\s*[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" + }, + "Image": { + "$id": "Image", + "description": "Describes an image", + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "description": "URL to the image. This can be a data url or an remote url", + "type": "string", + "format": "uri" + }, + "size_type": { + "description": "The size of the image. The network policy can define the default dimensions of each type", + "type": "string", + "enum": [ + "xs", + "sm", + "md", + "lg", + "xl", + "custom" + ] + }, + "width": { + "description": "Width of the image in pixels", + "type": "string" + }, + "height": { + "description": "Height of the image in pixels", + "type": "string" + } + } + }, + "Intent": { + "$id": "Intent", + "description": "The intent to buy or avail a product or a service. The BAP can declare the intent of the consumer containing
This has properties like descriptor,provider,fulfillment,payment,category,offer,item,tags
This is typically used by the BAP to send the purpose of the user's search to the BPP. This will be used by the BPP to find products or services it offers that may match the user's intent.
For example, in Mobility, the mobility consumer declares a mobility intent. In this case, the mobility consumer declares information that describes various aspects of their journey like,
For example, in health domain, a consumer declares the intent for a lab booking the describes various aspects of their booking like,", + "type": "object", + "additionalProperties": false, + "properties": { + "descriptor": { + "description": "A raw description of the search intent. Free text search strings, raw audio, etc can be sent in this object.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Descriptor" + } + ] + }, + "provider": { + "description": "The provider from which the customer wants to place to the order from", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Provider" + } + ] + }, + "fulfillment": { + "description": "Details on how the customer wants their order fulfilled", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Fulfillment" + } + ] + }, + "payment": { + "description": "Details on how the customer wants to pay for the order", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Payment" + } + ] + }, + "category": { + "description": "Details on the item category", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Category" + } + ] + }, + "offer": { + "description": "details on the offer the customer wants to avail", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Offer" + } + ] + }, + "item": { + "description": "Details of the item that the consumer wants to order", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Item" + } + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "ItemQuantity": { + "$id": "ItemQuantity", + "description": "Describes the count or amount of an item", + "type": "object", + "additionalProperties": false, + "properties": { + "allocated": { + "description": "This represents the exact quantity allocated for purchase of the item.", + "type": "object", + "additionalProperties": false, + "properties": { + "count": { + "type": "integer", + "minimum": 0 + }, + "measure": { + "$ref": "definitions.json#/$defs/Scalar" + } + } + }, + "available": { + "description": "This represents the exact quantity available for purchase of the item. The buyer can only purchase multiples of this", + "type": "object", + "additionalProperties": false, + "properties": { + "count": { + "type": "integer", + "minimum": 0 + }, + "measure": { + "$ref": "definitions.json#/$defs/Scalar" + } + } + }, + "maximum": { + "description": "This represents the maximum quantity allowed for purchase of the item", + "type": "object", + "additionalProperties": false, + "properties": { + "count": { + "type": "integer", + "minimum": 1 + }, + "measure": { + "$ref": "definitions.json#/$defs/Scalar" + } + } + }, + "minimum": { + "description": "This represents the minimum quantity allowed for purchase of the item", + "type": "object", + "additionalProperties": false, + "properties": { + "count": { + "type": "integer", + "minimum": 0 + }, + "measure": { + "$ref": "definitions.json#/$defs/Scalar" + } + } + }, + "selected": { + "description": "This represents the quantity selected for purchase of the item", + "type": "object", + "additionalProperties": false, + "properties": { + "count": { + "type": "integer", + "minimum": 0 + }, + "measure": { + "$ref": "definitions.json#/$defs/Scalar" + } + } + }, + "unitized": { + "description": "This represents the quantity available in a single unit of the item", + "type": "object", + "additionalProperties": false, + "properties": { + "count": { + "type": "integer", + "minimum": 1, + "maximum": 1 + }, + "measure": { + "$ref": "definitions.json#/$defs/Scalar" + } + } + } + } + }, + "Item": { + "$id": "Item", + "description": "Describes a product or a service offered to the end consumer by the provider. In the mobility sector, it can represent a fare product like one way journey. In the logistics sector, it can represent the delivery service offering. In the retail domain it can represent a product like a grocery item.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "ID of the item.", + "type": "string" + }, + "parent_item_id": { + "description": "ID of the item, this item is a variant of", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Item/properties/id" + } + ] + }, + "parent_item_quantity": { + "description": "The number of units of the parent item this item is a multiple of", + "allOf": [ + { + "$ref": "definitions.json#/$defs/ItemQuantity" + } + ] + }, + "descriptor": { + "description": "Physical description of the item", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Descriptor" + } + ] + }, + "creator": { + "description": "The creator of this item", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Organization" + } + ] + }, + "price": { + "description": "The price of this item, if it has intrinsic value", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Price" + } + ] + }, + "quantity": { + "description": "The selling quantity of the item", + "allOf": [ + { + "$ref": "definitions.json#/$defs/ItemQuantity" + } + ] + }, + "category_ids": { + "description": "Categories this item can be listed under", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "definitions.json#/$defs/Category/properties/id" + } + ] + } + }, + "fulfillment_ids": { + "description": "Modes through which this item can be fulfilled", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "definitions.json#/$defs/Fulfillment/properties/id" + } + ] + } + }, + "location_ids": { + "description": "Provider Locations this item is available in", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "definitions.json#/$defs/Location/properties/id" + } + ] + } + }, + "payment_ids": { + "description": "Payment modalities through which this item can be ordered", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "definitions.json#/$defs/Payment/properties/id" + } + ] + } + }, + "add_ons": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/AddOn" + } + }, + "cancellation_terms": { + "description": "Cancellation terms of this item", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/CancellationTerm" + } + }, + "refund_terms": { + "description": "Refund terms of this item", + "type": "array", + "items": { + "description": "Refund term of an item or an order", + "type": "object", + "additionalProperties": false, + "properties": { + "fulfillment_state": { + "description": "The state of fulfillment during which this term is applicable.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/State" + } + ] + }, + "refund_eligible": { + "description": "Indicates if cancellation will result in a refund", + "type": "boolean" + }, + "refund_within": { + "description": "Time within which refund will be processed after successful cancellation.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Time" + } + ] + }, + "refund_amount": { + "$ref": "definitions.json#/$defs/Price" + } + } + } + }, + "replacement_terms": { + "description": "Terms that are applicable be met when this item is replaced", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/ReplacementTerm" + } + }, + "return_terms": { + "description": "Terms that are applicable when this item is returned", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/ReturnTerm" + } + }, + "xinput": { + "description": "Additional input required from the customer to purchase / avail this item", + "allOf": [ + { + "$ref": "definitions.json#/$defs/XInput" + } + ] + }, + "time": { + "description": "Temporal attributes of this item. This property is used when the item exists on the catalog only for a limited period of time.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Time" + } + ] + }, + "rateable": { + "description": "Whether this item can be rated", + "type": "boolean" + }, + "rating": { + "description": "The rating of the item", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Rating/properties/value" + } + ] + }, + "matched": { + "description": "Whether this item is an exact match of the request", + "type": "boolean" + }, + "related": { + "description": "Whether this item is a related item to the exactly matched item", + "type": "boolean" + }, + "recommended": { + "description": "Whether this item is a recommended item to a response", + "type": "boolean" + }, + "ttl": { + "description": "Time to live in seconds for an instance of this schema", + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "Location": { + "$id": "Location", + "description": "The physical location of something", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "map_url": { + "description": "The url to the map of the location. This can be a globally recognized map url or the one specified by the network policy.", + "type": "string", + "format": "uri" + }, + "gps": { + "description": "The GPS co-ordinates of this location.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Gps" + } + ] + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "address": { + "description": "The address of this location.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Address" + } + ] + }, + "city": { + "description": "The city this location is, or is located within", + "allOf": [ + { + "$ref": "definitions.json#/$defs/City" + } + ] + }, + "district": { + "description": "The state this location is, or is located within", + "type": "string" + }, + "state": { + "description": "The state this location is, or is located within", + "allOf": [ + { + "$ref": "definitions.json#/$defs/State" + } + ] + }, + "country": { + "description": "The country this location is, or is located within", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Country" + } + ] + }, + "area_code": { + "type": "string" + }, + "circle": { + "$ref": "definitions.json#/$defs/Circle" + }, + "polygon": { + "description": "The boundary polygon of this location", + "type": "string" + }, + "3dspace": { + "description": "The three dimensional region describing this location", + "type": "string" + }, + "rating": { + "description": "The rating of this location", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Rating/properties/value" + } + ] + } + } + }, + "MediaFile": { + "$id": "MediaFile", + "description": "This object contains a url to a media file.", + "type": "object", + "additionalProperties": false, + "properties": { + "mimetype": { + "description": "indicates the nature and format of the document, file, or assortment of bytes. MIME types are defined and standardized in IETF's RFC 6838", + "type": "string" + }, + "url": { + "description": "The URL of the file", + "type": "string", + "format": "uri" + }, + "signature": { + "description": "The digital signature of the file signed by the sender", + "type": "string" + }, + "dsa": { + "description": "The signing algorithm used by the sender", + "type": "string" + } + } + }, + "Offer": { + "$id": "Offer", + "description": "An offer associated with a catalog. This is typically used to promote a particular product and enable more purchases.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "location_ids": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Location/properties/id" + } + }, + "category_ids": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Category/properties/id" + } + }, + "item_ids": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Item/properties/id" + } + }, + "time": { + "$ref": "definitions.json#/$defs/Time" + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "Option": { + "$id": "Option", + "description": "Describes a selectable option", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + } + } + }, + "Order": { + "$id": "Order", + "description": "Describes a legal purchase order. It contains the complete details of the legal contract created between the buyer and the seller.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "Human-readable ID of the order. This is generated at the BPP layer. The BPP can either generate order id within its system or forward the order ID created at the provider level." + }, + "ref_order_ids": { + "description": "A list of order IDs to link this order to previous orders.", + "type": "array", + "items": { + "type": "string", + "description": "ID of a previous order" + } + }, + "status": { + "description": "Status of the order. Allowed values can be defined by the network policy", + "type": "string", + "enum": [ + "ACTIVE", + "COMPLETE", + "CANCELLED", + "COMPLETED", + "SOFT_CANCEL" + ] + }, + "type": { + "description": "This is used to indicate the type of order being created to BPPs. Sometimes orders can be linked to previous orders, like a replacement order in a retail domain. A follow-up consultation in healthcare domain. A single order part of a subscription order. The list of order types can be standardized at the network level.", + "type": "string", + "default": "DEFAULT", + "enum": [ + "DRAFT", + "DEFAULT" + ] + }, + "provider": { + "description": "Details of the provider whose catalog items have been selected.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Provider" + } + ] + }, + "items": { + "description": "The items purchased / availed in this order", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Item" + } + }, + "add_ons": { + "description": "The add-ons purchased / availed in this order", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/AddOn" + } + }, + "offers": { + "description": "The offers applied in this order", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Offer" + } + }, + "billing": { + "description": "The billing details of this order", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Billing" + } + ] + }, + "fulfillments": { + "description": "The fulfillments involved in completing this order", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Fulfillment" + } + }, + "cancellation": { + "description": "The cancellation details of this order", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Cancellation" + } + ] + }, + "cancellation_terms": { + "description": "Cancellation terms of this item", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/CancellationTerm" + } + }, + "documents": { + "type": "array", + "items": { + "description": "Documnents associated to the order", + "type": "object", + "additionalProperties": false, + "properties": { + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "mime_type": { + "description": "This field indicates the nature and format of the form received by querying the url. MIME types are defined and standardized in IETF's RFC 6838.", + "type": "string", + "enum": [ + "text/html", + "application/html", + "application/xml", + "application/pdf" + ] + }, + "url": { + "description": "The URL from where the form can be fetched. The content fetched from the url must be processed as per the mime_type specified in this object.", + "type": "string", + "format": "uri" + } + } + } + }, + "refund_terms": { + "description": "Refund terms of this item", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Item/properties/refund_terms/items" + } + }, + "replacement_terms": { + "description": "Replacement terms of this item", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/ReplacementTerm" + } + }, + "return_terms": { + "description": "Return terms of this item", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/ReturnTerm" + } + }, + "quote": { + "description": "The mutually agreed upon quotation for this order.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Quotation" + } + ] + }, + "payments": { + "description": "The terms of settlement for this order", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Payment" + } + }, + "created_at": { + "description": "The date-time of creation of this order", + "type": "string", + "format": "date-time" + }, + "updated_at": { + "description": "The date-time of updated of this order", + "type": "string", + "format": "date-time" + }, + "xinput": { + "description": "Additional input required from the customer to confirm this order", + "allOf": [ + { + "$ref": "definitions.json#/$defs/XInput" + } + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "Organization": { + "$id": "Organization", + "description": "An organization. Usually a recognized business entity.", + "type": "object", + "additionalProperties": false, + "properties": { + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "address": { + "description": "The postal address of the organization", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Address" + } + ] + }, + "state": { + "description": "The state where the organization's address is registered", + "allOf": [ + { + "$ref": "definitions.json#/$defs/State" + } + ] + }, + "city": { + "description": "The city where the the organization's address is registered", + "allOf": [ + { + "$ref": "definitions.json#/$defs/City" + } + ] + }, + "contact": { + "$ref": "definitions.json#/$defs/Contact" + } + } + }, + "Payment": { + "$id": "Payment", + "description": "Describes the terms of settlement between the BAP and the BPP for a single transaction. When instantiated, this object contains
  1. the amount that has to be settled,
  2. The payment destination destination details
  3. When the settlement should happen, and
  4. A transaction reference ID
. During a transaction, the BPP reserves the right to decide the terms of payment. However, the BAP can send its terms to the BPP first. If the BPP does not agree to those terms, it must overwrite the terms and return them to the BAP. If overridden, the BAP must either agree to the terms sent by the BPP in order to preserve the provider's autonomy, or abort the transaction. In case of such disagreements, the BAP and the BPP can perform offline negotiations on the payment terms. Once an agreement is reached, the BAP and BPP can resume transactions.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "ID of the payment term that can be referred at an item or an order level in a catalog", + "type": "string" + }, + "collected_by": { + "description": "This field indicates who is the collector of payment. The BAP can set this value to 'bap' if it wants to collect the payment first and settle it to the BPP. If the BPP agrees to those terms, the BPP should not send the payment url. Alternatively, the BPP can set this field with the value 'bpp' if it wants the payment to be made directly.", + "type": "string" + }, + "url": { + "type": "string", + "description": "A payment url to be called by the BAP. If empty, then the payment is to be done offline. The details of payment should be present in the params object. If tl_method = http/get, then the payment details will be sent as url params. Two url param values, ```$transaction_id``` and ```$amount``` are mandatory.", + "format": "uri" + }, + "tl_method": { + "type": "string" + }, + "params": { + "type": "object", + "additionalProperties": false, + "properties": { + "transaction_id": { + "type": "string", + "description": "The reference transaction ID associated with a payment activity" + }, + "amount": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "bank_code": { + "type": "string" + }, + "bank_account_number": { + "type": "string" + }, + "virtual_payment_address": { + "type": "string" + }, + "source_bank_code": { + "type": "string" + }, + "source_bank_account_number": { + "type": "string" + }, + "source_virtual_payment_address": { + "type": "string" + } + } + }, + "type": { + "type": "string", + "enum": [ + "PRE-ORDER", + "PRE-FULFILLMENT", + "ON-FULFILLMENT", + "POST-FULFILLMENT", + "ON-ORDER", + "PART-PAYMENT" + ] + }, + "status": { + "type": "string", + "enum": [ + "PAID", + "NOT-PAID" + ] + }, + "time": { + "$ref": "definitions.json#/$defs/Time" + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "Person": { + "$id": "Person", + "description": "Describes a person as any individual", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "Describes the identity of the person" + }, + "url": { + "description": "Profile url of the person", + "type": "string", + "format": "uri" + }, + "name": { + "description": "the name of the person", + "type": "string" + }, + "image": { + "$ref": "definitions.json#/$defs/Image" + }, + "age": { + "description": "Age of the person", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Duration" + } + ] + }, + "dob": { + "description": "Date of birth of the person", + "type": "string", + "format": "date" + }, + "gender": { + "type": "string", + "description": "Gender of something, typically a Person, but possibly also fictional characters, animals, etc. While Male and Female may be used, text strings are also acceptable for people who do not identify as a binary gender.Allowed values for this field can be published in the network policy" + }, + "creds": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Credential" + } + }, + "languages": { + "type": "array", + "items": { + "description": "Describes a language known to the person.", + "type": "object", + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "skills": { + "type": "array", + "items": { + "description": "Describes a skill of the person.", + "type": "object", + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "Price": { + "$id": "Price", + "description": "Describes the price of a product or service", + "type": "object", + "additionalProperties": false, + "properties": { + "currency": { + "type": "string" + }, + "value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "estimated_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "computed_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "listed_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "offered_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "minimum_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "maximum_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + } + } + }, + "Provider": { + "$id": "Provider", + "description": "Describes the catalog of a business.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "Id of the provider" + }, + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "category_id": { + "type": "string", + "description": "Category Id of the provider at the BPP-level catalog" + }, + "rating": { + "$ref": "definitions.json#/$defs/Rating/properties/value" + }, + "time": { + "$ref": "definitions.json#/$defs/Time" + }, + "categories": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Category" + } + }, + "fulfillments": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Fulfillment" + } + }, + "payments": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Payment" + } + }, + "locations": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Location" + } + }, + "offers": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Offer" + } + }, + "items": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Item" + } + }, + "exp": { + "type": "string", + "description": "Time after which catalog has to be refreshed", + "format": "date-time" + }, + "rateable": { + "description": "Whether this provider can be rated or not", + "type": "boolean" + }, + "ttl": { + "description": "The time-to-live in seconds, for this object. This can be overriden at deeper levels. A value of -1 indicates that this object is not cacheable.", + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/TagGroup" + } + } + } + }, + "Quotation": { + "$id": "Quotation", + "description": "Describes a quote. It is the estimated price of products or services from the BPP.
This has properties like price, breakup, ttl", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "ID of the quote.", + "type": "string", + "format": "uuid" + }, + "price": { + "description": "The total quoted price", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Price" + } + ] + }, + "breakup": { + "description": "the breakup of the total quoted price", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "item": { + "$ref": "definitions.json#/$defs/Item" + }, + "title": { + "type": "string" + }, + "price": { + "$ref": "definitions.json#/$defs/Price" + } + } + } + }, + "ttl": { + "$ref": "definitions.json#/$defs/Duration" + } + } + }, + "Rating": { + "$id": "Rating", + "description": "Describes the rating of an entity", + "type": "object", + "additionalProperties": false, + "properties": { + "rating_category": { + "description": "Category of the entity being rated", + "type": "string", + "enum": [ + "Item", + "Order", + "Fulfillment", + "Provider", + "Agent", + "Support" + ] + }, + "id": { + "description": "Id of the object being rated", + "type": "string" + }, + "value": { + "description": "Rating value given to the object. This can be a single value or can also contain an inequality operator like gt, gte, lt, lte. This can also contain an inequality expression containing logical operators like && and ||.", + "type": "string" + } + } + }, + "Region": { + "$id": "Region", + "description": "Describes an arbitrary region of space. The network policy should contain a published list of supported regions by the network.", + "type": "object", + "additionalProperties": false, + "properties": { + "dimensions": { + "description": "The number of dimensions that are used to describe any point inside that region. The most common dimensionality of a region is 2, that represents an area on a map. There are regions on the map that can be approximated to one-dimensional regions like roads, railway lines, or shipping lines. 3 dimensional regions are rarer, but are gaining popularity as flying drones are being adopted for various fulfillment services.", + "type": "string", + "enum": [ + "1", + "2", + "3" + ] + }, + "type": { + "description": "The type of region. This is used to specify the granularity of the region represented by this object. Various examples of two-dimensional region types are city, country, state, district, and so on. The network policy should contain a list of all possible region types supported by the network.", + "type": "string" + }, + "name": { + "type": "string", + "description": "Name of the region as specified on the map where that region exists." + }, + "code": { + "type": "string", + "description": "A standard code representing the region. This should be interpreted in the same way by all network participants." + }, + "boundary": { + "type": "string", + "description": "A string representing the boundary of the region. One-dimensional regions are represented by polylines. Two-dimensional regions are represented by polygons, and three-dimensional regions can represented by polyhedra." + }, + "map_url": { + "type": "string", + "description": "The url to the map of the region. This can be a globally recognized map or the one specified by the network policy." + } + } + }, + "ReplacementTerm": { + "$id": "ReplacementTerm", + "description": "The replacement policy of an item or an order", + "type": "object", + "additionalProperties": false, + "properties": { + "fulfillment_state": { + "description": "The state of fulfillment during which this term is applicable.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/State" + } + ] + }, + "replace_within": { + "description": "Applicable only for buyer managed returns where the buyer has to replace the item before a certain date-time, failing which they will not be eligible for replacement", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Time" + } + ] + }, + "external_ref": { + "$ref": "definitions.json#/$defs/MediaFile" + } + } + }, + "ReturnTerm": { + "$id": "ReturnTerm", + "description": "Describes the return policy of an item or an order", + "type": "object", + "additionalProperties": false, + "properties": { + "fulfillment_state": { + "description": "The state of fulfillment during which this term IETF''s applicable.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/State" + } + ] + }, + "return_eligible": { + "description": "Indicates whether the item is eligible for return", + "type": "boolean" + }, + "return_time": { + "description": "Applicable only for buyer managed returns where the buyer has to return the item to the origin before a certain date-time, failing which they will not be eligible for refund.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Time" + } + ] + }, + "return_location": { + "description": "The location where the item or order must / will be returned to", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Location" + } + ] + }, + "fulfillment_managed_by": { + "description": "The entity that will perform the return", + "type": "string", + "enum": [ + "CONSUMER", + "PROVIDER" + ] + } + } + }, + "Scalar": { + "$id": "Scalar", + "description": "Describes a scalar", + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "CONSTANT", + "VARIABLE" + ] + }, + "value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "estimated_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "computed_value": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "range": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "$ref": "definitions.json#/$defs/DecimalValue" + }, + "max": { + "$ref": "definitions.json#/$defs/DecimalValue" + } + } + }, + "unit": { + "type": "string" + } + } + }, + "Schedule": { + "$id": "Schedule", + "description": "Describes schedule as a repeating time period used to describe a regularly recurring event. At a minimum a schedule will specify frequency which describes the interval between occurrences of the event. Additional information can be provided to specify the schedule more precisely. This includes identifying the timestamps(s) of when the event will take place. Schedules may also have holidays to exclude a specific day from the schedule.
This has properties like frequency, holidays, times", + "type": "object", + "additionalProperties": false, + "properties": { + "frequency": { + "$ref": "definitions.json#/$defs/Duration" + }, + "holidays": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + }, + "times": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + } + } + }, + "State": { + "$id": "State", + "description": "A bounded geopolitical region of governance inside a country.", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the state" + }, + "code": { + "type": "string", + "description": "State code as per country or international standards" + } + } + }, + "Stop": { + "$id": "Stop", + "description": "A logical point in space and time during the fulfillment of an order.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "parent_stop_id": { + "type": "string" + }, + "location": { + "description": "Location of the stop", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Location" + } + ] + }, + "type": { + "description": "The type of stop. Allowed values of this property can be defined by the network policy.", + "type": "string" + }, + "time": { + "description": "Timings applicable at the stop.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Time" + } + ] + }, + "instructions": { + "description": "Instructions that need to be followed at the stop", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Descriptor" + } + ] + }, + "contact": { + "description": "Contact details of the stop", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Contact" + } + ] + }, + "person": { + "description": "The details of the person present at the stop", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Person" + } + ] + }, + "authorization": { + "$ref": "definitions.json#/$defs/Authorization" + } + } + }, + "Support": { + "$id": "Support", + "description": "Details of customer support", + "type": "object", + "additionalProperties": false, + "properties": { + "ref_id": { + "type": "string" + }, + "callback_phone": { + "type": "string", + "format": "phone" + }, + "phone": { + "type": "string", + "format": "phone" + }, + "email": { + "type": "string", + "format": "email" + }, + "url": { + "type": "string", + "format": "uri" + } + } + }, + "Tag": { + "$id": "Tag", + "description": "Describes a tag. This is used to contain extended metadata. This object can be added as a property to any schema to describe extended attributes. For BAPs, tags can be sent during search to optimize and filter search results. BPPs can use tags to index their catalog to allow better search functionality. Tags are sent by the BPP as part of the catalog response in the `on_search` callback. Tags are also meant for display purposes. Upon receiving a tag, BAPs are meant to render them as name-value pairs. This is particularly useful when rendering tabular information about a product or service.", + "type": "object", + "additionalProperties": false, + "properties": { + "descriptor": { + "description": "Description of the Tag, can be used to store detailed information.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Descriptor" + } + ] + }, + "value": { + "description": "The value of the tag. This set by the BPP and rendered as-is by the BAP.", + "type": "string" + }, + "display": { + "description": "This value indicates if the tag is intended for display purposes. If set to `true`, then this tag must be displayed. If it is set to `false`, it should not be displayed. This value can override the group display value.", + "type": "boolean" + } + } + }, + "TagGroup": { + "$id": "TagGroup", + "description": "A collection of tag objects with group level attributes. For detailed documentation on the Tags and Tag Groups schema go to https://github.com/beckn/protocol-specifications/discussions/316", + "type": "object", + "additionalProperties": false, + "properties": { + "display": { + "description": "Indicates the display properties of the tag group. If display is set to false, then the group will not be displayed. If it is set to true, it should be displayed. However, group-level display properties can be overriden by individual tag-level display property. As this schema is purely for catalog display purposes, it is not recommended to send this value during search.", + "type": "boolean", + "default": true + }, + "descriptor": { + "description": "Description of the TagGroup, can be used to store detailed information.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Descriptor" + } + ] + }, + "list": { + "description": "An array of Tag objects listed under this group. This property can be set by BAPs during search to narrow the `search` and achieve more relevant results. When received during `on_search`, BAPs must render this list under the heading described by the `name` property of this schema.", + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Tag" + } + } + } + }, + "Time": { + "$id": "Time", + "description": "Describes time in its various forms. It can be a single point in time; duration; or a structured timetable of operations
This has properties like label, time stamp,duration,range, days, schedule", + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "duration": { + "$ref": "definitions.json#/$defs/Duration" + }, + "range": { + "type": "object", + "additionalProperties": false, + "properties": { + "start": { + "type": "string", + "format": "date-time" + }, + "end": { + "type": "string", + "format": "date-time" + } + } + }, + "days": { + "type": "string", + "description": "comma separated values representing days of the week" + }, + "schedule": { + "$ref": "definitions.json#/$defs/Schedule" + } + } + }, + "Tracking": { + "$id": "Tracking", + "description": "Contains tracking information that can be used by the BAP to track the fulfillment of an order in real-time. which is useful for knowing the location of time sensitive deliveries.", + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "description": "A unique tracking reference number", + "type": "string" + }, + "url": { + "description": "A URL to the tracking endpoint. This can be a link to a tracking webpage, a webhook URL created by the BAP where BPP can push the tracking data, or a GET url creaed by the BPP which the BAP can poll to get the tracking data. It can also be a websocket URL where the BPP can push real-time tracking data.", + "type": "string", + "format": "uri" + }, + "location": { + "description": "In case there is no real-time tracking endpoint available, this field will contain the latest location of the entity being tracked. The BPP will update this value everytime the BAP calls the track API.", + "allOf": [ + { + "$ref": "definitions.json#/$defs/Location" + } + ] + }, + "status": { + "description": "This value indicates if the tracking is currently active or not. If this value is `active`, then the BAP can begin tracking the order. If this value is `inactive`, the tracking URL is considered to be expired and the BAP should stop tracking the order.", + "type": "string", + "enum": [ + "active", + "inactive" + ] + } + } + }, + "Vehicle": { + "$id": "Vehicle", + "description": "Describes a vehicle is a device that is designed or used to transport people or cargo over land, water, air, or through space.
This has properties like category, capacity, make, model, size,variant,color,energy_type,registration", + "type": "object", + "additionalProperties": false, + "properties": { + "category": { + "type": "string" + }, + "capacity": { + "type": "integer" + }, + "make": { + "type": "string" + }, + "model": { + "type": "string" + }, + "size": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "color": { + "type": "string" + }, + "energy_type": { + "type": "string" + }, + "registration": { + "type": "string" + }, + "wheels_count": { + "type": "string" + }, + "cargo_volumne": { + "type": "string" + }, + "wheelchair_access": { + "type": "string" + }, + "code": { + "type": "string" + }, + "emission_standard": { + "type": "string" + } + } + }, + "XInput": { + "$id": "XInput", + "description": "Contains any additional or extended inputs required to confirm an order. This is typically a Form Input. Sometimes, selection of catalog elements is not enough for the BPP to confirm an order. For example, to confirm a flight ticket, the airline requires details of the passengers along with information on baggage, identity, in addition to the class of ticket. Similarly, a logistics company may require details on the nature of shipment in order to confirm the shipping. A recruiting firm may require additional details on the applicant in order to confirm a job application. For all such purposes, the BPP can choose to send this object attached to any object in the catalog that is required to be sent while placing the order. This object can typically be sent at an item level or at the order level. The item level XInput will override the Order level XInput as it indicates a special requirement of information for that particular item. Hence the BAP must render a separate form for the Item and another form at the Order level before confirmation.", + "type": "object", + "additionalProperties": false, + "properties": { + "head": { + "description": "Provides the header information for the xinput.", + "type": "object", + "additionalProperties": false, + "properties": { + "descriptor": { + "$ref": "definitions.json#/$defs/Descriptor" + }, + "index": { + "type": "object", + "additionalProperties": false, + "properties": { + "min": { + "type": "integer" + }, + "cur": { + "type": "integer" + }, + "max": { + "type": "integer" + } + } + }, + "headings": { + "type": "array", + "items": { + "type": "string", + "description": "The heading names of the forms" + } + } + } + }, + "form": { + "$ref": "definitions.json#/$defs/Form" + }, + "form_response": { + "description": "Describes the response to a form submission", + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "description": "Contains the status of form submission.", + "type": "string" + }, + "signature": { + "type": "string" + }, + "submission_id": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "definitions.json#/$defs/Error" + } + } + } + }, + "required": { + "description": "Indicates whether the form data is mandatorily required by the BPP to confirm the order.", + "type": "boolean" + } + } + } + } + } \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_cancel.json b/config/onix/schemas/core/v1.1.0/on_cancel.json new file mode 100644 index 0000000..4abb380 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_cancel.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_cancel", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_cancel" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context", + "message" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_confirm.json b/config/onix/schemas/core/v1.1.0/on_confirm.json new file mode 100644 index 0000000..4abea80 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_confirm.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_confirm", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_confirm" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_init.json b/config/onix/schemas/core/v1.1.0/on_init.json new file mode 100644 index 0000000..18c44f3 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_init.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_init", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_init" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_rating.json b/config/onix/schemas/core/v1.1.0/on_rating.json new file mode 100644 index 0000000..0b9898b --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_rating.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_rating", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_rating" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "feedback_form": { + "description": "A feedback form to allow the user to provide additional information on the rating provided", + "allOf": [ + { + "$ref": "./definitions.json#/$defs/XInput" + } + ] + } + } + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context", + "message" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_search.json b/config/onix/schemas/core/v1.1.0/on_search.json new file mode 100644 index 0000000..854c6f9 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_search.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_search", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_search" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "catalog": { + "$ref": "./definitions.json#/$defs/Catalog" + } + }, + "required": [ + "catalog" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_select.json b/config/onix/schemas/core/v1.1.0/on_select.json new file mode 100644 index 0000000..956c6a8 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_select.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_select", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_select" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + } + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_status.json b/config/onix/schemas/core/v1.1.0/on_status.json new file mode 100644 index 0000000..00167e5 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_status.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_status", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_status" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_support.json b/config/onix/schemas/core/v1.1.0/on_support.json new file mode 100644 index 0000000..7ba94f0 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_support.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_support", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_support" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "support": { + "$ref": "./definitions.json#/$defs/Support" + } + } + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context", + "message" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_track.json b/config/onix/schemas/core/v1.1.0/on_track.json new file mode 100644 index 0000000..cbaf906 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_track.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_track", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_track" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "tracking": { + "$ref": "./definitions.json#/$defs/Tracking" + } + }, + "required": [ + "tracking" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "context", + "message" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_update.json b/config/onix/schemas/core/v1.1.0/on_update.json new file mode 100644 index 0000000..74de03c --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/on_update.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_update", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "on_update" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + }, + "error": { + "$ref": "./definitions.json#/$defs/Error" + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/search.json b/config/onix/schemas/core/v1.1.0/search.json new file mode 100644 index 0000000..7faae89 --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/search.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "search", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + } + ] + }, + "message": { + "properties": { + "intent": { + "$ref": "./definitions.json#/$defs/Intent" + } + }, + "type": "object" + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/select.json b/config/onix/schemas/core/v1.1.0/select.json new file mode 100644 index 0000000..7151fff --- /dev/null +++ b/config/onix/schemas/core/v1.1.0/select.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "select", + "type": "object", + "properties": { + "context": { + "allOf": [ + { + "$ref": "./definitions.json#/$defs/Context" + }, + { + "type": "object", + "properties": { + "action": { + "enum": [ + "select" + ] + } + }, + "required": [ + "action" + ] + } + ] + }, + "message": { + "type": "object", + "additionalProperties": false, + "properties": { + "order": { + "$ref": "./definitions.json#/$defs/Order" + } + }, + "required": [ + "order" + ] + } + }, + "required": [ + "message", + "context" + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/cancel.json b/config/onix/schemas/ondc_trv10/v2.0.0/cancel.json new file mode 100644 index 0000000..b53fedb --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/cancel.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "cancel", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/cancel.json#" + }, + { + "$ref": "init.json#/allOf/2" + }, + { + "properties": { + "message": { + "type": "object", + "properties": { + "order_id": { + "type": "string" + }, + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["SOFT_CANCEL", "CONFIRM_CANCEL"] + } + }, + "required": ["code"] + }, + "cancellation_reason_id": { + "type": "string", + "pattern": "^[0-9]+$" + } + }, + "required": ["order_id", "descriptor", "cancellation_reason_id"] + } + } + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/confirm.json b/config/onix/schemas/ondc_trv10/v2.0.0/confirm.json new file mode 100644 index 0000000..25f4e01 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/confirm.json @@ -0,0 +1,463 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "confirm", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/confirm.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "$ref": "./init.json#/allOf/2" + }, + { + "allOf": [ + { + "$ref": "./on_select.json#/allOf/5" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "items": { + "allOf": [ + { + "properties": { + "customer": { + "properties": { + "contact": { + "properties": { + "phone": { + "type": "string", + "pattern": "^\\+?[1-9]\\d{1,14}$" + } + }, + "required": [ + "phone" + ] + }, + "person": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "contact", + "person" + ] + } + }, + "required": [ + "customer" + ] + } + ] + } + } + } + } + } + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "./init.json#/allOf/7" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "payments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "params": { + "type": "object", + "properties": { + "amount": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + }, + "type": { + "type": "string", + "enum": [ + "PRE-ORDER", + "ON-FULFILLMENT", + "POST-FULFILLMENT" + ] + }, + "status": { + "type": "string", + "enum": [ + "PAID", + "NOT-PAID" + ] + }, + "collected_by": { + "type": "string", + "enum": [ + "BAP", + "BPP" + ] + }, + "tags": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "SETTLEMENT_TERMS", + "BUYER_FINDER_FEES" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "descriptor": { + "properties": { + "code": { + "const": "SETTLEMENT_TERMS" + } + } + } + } + }, + "then": { + "properties": { + "list": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "STATIC_TERMS" + } + } + }, + "value": { + "type": "string", + "format": "uri" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_BASIS" + } + } + }, + "value": { + "type": "string", + "enum": [ + "DELIVERY" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_WINDOW" + } + } + }, + "value": { + "type": "string" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "DELAY_INTEREST" + } + } + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_TYPE" + } + } + }, + "value": { + "type": "string", + "enum": [ + "upi", + "neft", + "rtgs", + "UPI", + "NEFT", + "RTGS" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_AMOUNT" + } + } + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "MANDATORY_ARBITRATION" + } + } + }, + "value": { + "type": "string", + "enum": [ + "true", + "false" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "COURT_JURISDICTION" + } + } + }, + "value": { + "type": "string" + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "descriptor": { + "properties": { + "code": { + "const": "BUYER_FINDER_FEES" + } + } + } + } + }, + "then": { + "properties": { + "list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "enum": [ + "BUYER_FINDER_FEES_PERCENTAGE" + ] + } + } + }, + "value": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + } + } + } + } + } + ] + } + }, + "required": [ + "descriptor" + ] + } + } + }, + "required": [ + "type", + "status", + "collected_by", + "tags" + ] + } + } + } + } + } + } + } + } + ] + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "payments": { + "type": "array", + "items": { + "properties": { + "type": { + "type": "string" + }, + "params": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string" + } + } + } + }, + "required": [ + "type" + ], + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "PRE-ORDER" + } + } + }, + "then": { + "properties": { + "params": { + "required": [ + "transaction_id" + ] + } + } + } + } + ] + } + } + }, + "required": [ + "payments" + ] + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "not": { + "required": [ + "id" + ] + } + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/init.json b/config/onix/schemas/ondc_trv10/v2.0.0/init.json new file mode 100644 index 0000000..395ba67 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/init.json @@ -0,0 +1,550 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "init", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/init.json#" + }, + { + "properties": { + "context": { + "type": "object", + "allOf": [ + { + "$ref": "./search.json#/properties/context/allOf/0" + }, + { + + "required": [ + "bpp_id", + "bpp_uri" + ] + } + ] + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "type": "object", + "properties": { + "provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + }, + "required": [ + "provider", + "items" + ] + } + } + } + } + }, + { + "$ref": "./confirm.json#/allOf/4" + }, + { + "$ref": "./on_select.json#/allOf/7" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "required": [ + "fulfillments" + ] + } + } + } + } + }, + { + "properties": { + "message": { + "type": "object", + "properties": { + "order": { + "type": "object", + "properties": { + "payments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "params": { + "type": "object", + "properties": { + "amount": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + }, + "type": { + "type": "string", + "enum": [ + "PRE-ORDER", + "ON-FULFILLMENT", + "POST-FULFILLMENT" + ] + }, + "status": { + "type": "string", + "enum": [ + "PAID", + "NOT-PAID" + ] + }, + "collected_by": { + "type": "string", + "enum": [ + "BAP", + "BPP" + ] + }, + "tags": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "SETTLEMENT_TERMS", + "BUYER_FINDER_FEES" + ] + } + } + } + }, + "required": [ + "descriptor" + ] + } + } + }, + "required": [ + "type", + "status", + "collected_by", + "tags" + ], + "allOf": [ + { + "if": { + "allOf": [ + { + "properties": { + "collected_by": { + "const": "BAP" + } + } + }, + { + "properties": { + "type": { + "const": "PRE-ORDER" + } + } + } + ] + }, + "then": { + "properties": { + "tags": { + "items": { + "if": { + "properties": { + "descriptor": { + "properties": { + "code": { + "const": "SETTLEMENT_TERMS" + } + } + } + } + }, + "then": { + "properties": { + "list": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "STATIC_TERMS" + } + } + }, + "value": { + "type": "string", + "format": "uri" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_BASIS" + } + } + }, + "value": { + "type": "string", + "enum": [ + "DELIVERY" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_WINDOW" + } + } + }, + "value": { + "type": "string" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "DELAY_INTEREST" + } + } + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + } + } + ] + } + } + } + } + } + } + } + }, + { + "if": { + "allOf": [ + { + "properties": { + "collected_by": { + "const": "BPP" + } + } + }, + { + "properties": { + "type": { + "const": "PRE-ORDER" + } + } + } + ] + }, + "then": { + "properties": { + "tags": { + "items": { + "if": { + "properties": { + "descriptor": { + "properties": { + "code": { + "const": "SETTLEMENT_TERMS" + } + } + } + } + }, + "then": { + "properties": { + "list": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "STATIC_TERMS" + } + } + }, + "value": { + "type": "string", + "format": "uri" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_BASIS" + } + } + }, + "value": { + "type": "string", + "enum": [ + "DELIVERY" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_WINDOW" + } + } + }, + "value": { + "type": "string" + } + } + } + } + ] + } + } + } + } + } + } + } + }, + { + "if": { + "allOf": [ + { + "properties": { + "collected_by": { + "const": "BPP" + } + } + }, + { + "properties": { + "type": { + "const": "ON-FULFILLMENT" + } + } + } + ] + }, + "then": { + "properties": { + "tags": { + "items": { + "if": { + "properties": { + "descriptor": { + "properties": { + "code": { + "const": "SETTLEMENT_TERMS" + } + } + } + } + }, + "then": { + "properties": { + "list": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "STATIC_TERMS" + } + } + }, + "value": { + "type": "string", + "format": "uri" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_BASIS" + } + } + }, + "value": { + "type": "string", + "enum": [ + "DELIVERY" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_WINDOW" + } + } + }, + "value": { + "type": "string" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "DELAY_INTEREST" + } + } + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + } + } + ] + } + } + } + } + } + } + } + } + ] + } + } + } + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "billing": { + "required": [ + "name" + ] + } + }, + "required": [ + "billing" + ] + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json new file mode 100644 index 0000000..dbcf8f0 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_cancel", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_cancel.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json new file mode 100644 index 0000000..4a0083e --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json @@ -0,0 +1,314 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_confirm", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_confirm.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": ["id"] + } + }, + "required": ["provider"] + } + } + } + } + }, + { + "$ref": "./confirm.json#/allOf/5" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "items": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "fulfillment_ids": { + "minItems": 1 + }, + "location_ids": { + "minItems": 1 + } + }, + "required": ["fulfillment_ids", "location_ids"] + } + } + } + } + } + } + } + }, + { + "$ref": "./confirm.json#/allOf/4/allOf/1" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DELIVERY"] + } + }, + "required": ["type"] + } + } + } + } + } + } + } + }, + { + "allOf": [ + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "quote": { + "type": "object", + "properties": { + "price": { + "type": "object", + "properties": { + "currency": { + "type": "string" + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + }, + "required": ["currency", "value"] + }, + "breakup": { + "type": "array", + "items": { + "type": "object", + "properties": { + "price": { + "type": "object", + "properties": { + "currency": { + "type": "string" + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + }, + "required": ["currency", "value"] + }, + "title": { + "type": "string", + "enum": [ + "BASE_FARE", + "DISTANCE_FARE", + "TAX", + "DISCOUNT", + "WAITING_CHARGE" + ] + } + }, + "required": ["price", "title"] + } + } + }, + "required": ["price", "breakup"] + } + }, + "required": ["quote"] + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "quote": { + "properties": { + "breakup": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "title": { + "const": "BASE_FARE" + }, + "price": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + }, + "required": ["value"] + } + }, + "required": ["title", "price"] + } + }, + { + "contains": { + "type": "object", + "properties": { + "title": { + "const": "DISTANCE_FARE" + }, + "price": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + }, + "required": ["value"] + } + }, + "required": ["title", "price"] + } + } + ] + } + } + } + } + } + } + } + } + } + ] + }, + { + "$ref": "./confirm.json#/allOf/6" + }, + { + "allOf": [ + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "cancellation_terms": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fulfillment_state": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "RIDE_ASSIGNED", + "RIDE_ENROUTE_PICKUP", + "RIDE_ARRIVED_PICKUP", + "RIDE_STARTED" + ] + } + }, + "required": ["code"] + } + }, + "required": ["descriptor"] + }, + "cancellation_fee": { + "oneOf": [ + { + "type": "object", + "properties": { + "percentage": { + "type": "string", + "pattern": "^(100(\\.0{1,2})?|([0-9]{1,2})(\\.\\d{1,2})?)$" + } + }, + "required": ["percentage"] + }, + { + "type": "object", + "properties": { + "amount": { + "type": "object", + "properties": { + "value": { + "type": "string", + "pattern": "^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)$" + }, + "currency": { + "type": "string" + } + }, + "required": ["currency", "value"] + } + }, + "required": ["amount"] + } + ] + } + }, + "required": ["fulfillment_state", "cancellation_fee"] + } + } + }, + "required": ["cancellation_terms"] + } + } + } + } + }, + { + "properties": { + "message": { + "type": "object" + } + }, + "required": ["message"] + } + ] + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_init.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_init.json new file mode 100644 index 0000000..fe7105c --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_init.json @@ -0,0 +1,317 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_init", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_init.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": ["id"] + } + }, + "required": ["provider"] + } + } + } + } + }, + { + "$ref": "./confirm.json#/allOf/5" + }, + { + "$ref": "./on_select.json#/allOf/6" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "items": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "fulfillment_ids": { + "minItems": 1 + }, + "location_ids": { + "minItems": 1 + } + }, + "required": ["fulfillment_ids", "location_ids"] + } + } + } + } + } + } + } + }, + { + "$ref": "./confirm.json#/allOf/4/allOf/1" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DELIVERY"] + } + }, + "required": ["type"] + } + } + } + } + } + } + } + }, + { + "allOf": [ + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "quote": { + "type": "object", + "properties": { + "price": { + "type": "object", + "properties": { + "currency": { + "type": "string" + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + }, + "required": ["currency", "value"] + }, + "breakup": { + "type": "array", + "items": { + "type": "object", + "properties": { + "price": { + "type": "object", + "properties": { + "currency": { + "type": "string" + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + }, + "required": ["currency", "value"] + }, + "title": { + "type": "string", + "enum": [ + "BASE_FARE", + "DISTANCE_FARE", + "TAX", + "DISCOUNT", + "WAITING_CHARGE" + ] + } + }, + "required": ["price", "title"] + } + } + }, + "required": ["price", "breakup"] + } + }, + "required": ["quote"] + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "quote": { + "properties": { + "breakup": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "title": { + "const": "BASE_FARE" + }, + "price": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + }, + "required": ["value"] + } + }, + "required": ["title", "price"] + } + }, + { + "contains": { + "type": "object", + "properties": { + "title": { + "const": "DISTANCE_FARE" + }, + "price": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + }, + "required": ["value"] + } + }, + "required": ["title", "price"] + } + } + ] + } + } + } + } + } + } + } + } + } + ] + }, + { + "$ref": "./confirm.json#/allOf/6" + }, + { + "allOf": [ + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "cancellation_terms": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fulfillment_state": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "RIDE_ASSIGNED", + "RIDE_ENROUTE_PICKUP", + "RIDE_ARRIVED_PICKUP", + "RIDE_STARTED" + ] + } + }, + "required": ["code"] + } + }, + "required": ["descriptor"] + }, + "cancellation_fee": { + "oneOf": [ + { + "type": "object", + "properties": { + "percentage": { + "type": "string", + "pattern": "^(100(\\.0{1,2})?|([0-9]{1,2})(\\.\\d{1,2})?)$" + } + }, + "required": ["percentage"] + }, + { + "type": "object", + "properties": { + "amount": { + "type": "object", + "properties": { + "value": { + "type": "string", + "pattern": "^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)$" + }, + "currency": { + "type": "string" + } + }, + "required": ["currency", "value"] + } + }, + "required": ["amount"] + } + ] + } + }, + "required": ["fulfillment_state", "cancellation_fee"] + } + } + }, + "required": ["cancellation_terms"] + } + } + } + } + }, + { + "properties": { + "message": { + "type": "object" + } + }, + "required": ["message"] + } + ] + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json new file mode 100644 index 0000000..684a1c3 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_rating", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_rating.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_search.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_search.json new file mode 100644 index 0000000..06d5a8b --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_search.json @@ -0,0 +1,644 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_search", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_search.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "properties": { + "message": { + "type": "object", + "properties": { + "catalog": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": [ + "name" + ] + }, + "providers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "descriptor": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "images": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": [ + "name" + ] + }, + "fulfillments": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "vehicle": { + "type": "object", + "properties": { + "category": { + "type": "string", + "enum": [ + "AUTO_RICKSHAW", + "CAB" + ] + } + }, + "required": [ + "category" + ] + }, + "type": { + "type": "string", + "enum": [ + "DELIVERY" + ] + } + }, + "required": [ + "id", + "vehicle", + "type" + ] + } + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "RIDE" + ] + } + }, + "required": [ + "code" + ] + }, + "price": { + "type": "object", + "properties": { + "value": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + }, + "required": [ + "value", + "currency" + ] + }, + "fulfillment_ids": { + "type": "array", + "minItems": 1 + }, + "payment_ids": { + "type": "array", + "minItems": 1 + } + }, + "required": [ + "id", + "descriptor", + "price", + "fulfillment_ids" + ] + } + } + }, + "required": [ + "id", + "items", + "fulfillments" + ] + } + } + }, + "required": [ + "providers" + ] + } + }, + "required": [ + "catalog" + ] + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "payments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "collected_by": { + "type": "string", + "enum": [ + "BAP", + "BPP" + ] + }, + "tags": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "SETTLEMENT_TERMS", + "BUYER_FINDER_FEES" + ] + } + } + } + }, + "required": [ + "descriptor" + ] + } + } + }, + "required": [ + "collected_by", + "tags" + ], + "allOf": [ + { + "if": { + "allOf": [ + { + "properties": { + "collected_by": { + "const": "BAP" + } + } + } + ] + }, + "then": { + "properties": { + "tags": { + "items": { + "if": { + "properties": { + "descriptor": { + "properties": { + "code": { + "const": "SETTLEMENT_TERMS" + } + } + } + } + }, + "then": { + "properties": { + "list": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "STATIC_TERMS" + } + } + }, + "value": { + "type": "string", + "format": "uri" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_TYPE" + } + } + }, + "value": { + "type": "string", + "enum": [ + "upi", + "neft", + "rtgs", + "UPI", + "NEFT", + "RTGS" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "DELAY_INTEREST" + } + } + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "MANDATORY_ARBITRATION" + } + } + }, + "value": { + "type": "string", + "enum": [ + "true", + "false" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "COURT_JURISDICTION" + } + } + }, + "value": { + "type": "string" + } + } + } + } + ] + } + } + } + } + } + } + } + }, + { + "if": { + "allOf": [ + { + "properties": { + "collected_by": { + "const": "BPP" + } + } + } + ] + }, + "then": { + "properties": { + "tags": { + "items": { + "if": { + "properties": { + "descriptor": { + "properties": { + "code": { + "const": "SETTLEMENT_TERMS" + } + } + } + } + }, + "then": { + "properties": { + "list": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "STATIC_TERMS" + } + } + }, + "value": { + "type": "string", + "format": "uri" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_TYPE" + } + } + }, + "value": { + "type": "string", + "enum": [ + "upi", + "neft", + "rtgs", + "UPI", + "NEFT", + "RTGS" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "DELAY_INTEREST" + } + } + }, + "value": { + "type": "string", + "pattern": "^\\d+(\\.\\d{1,2})?$" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "MANDATORY_ARBITRATION" + } + } + }, + "value": { + "type": "string", + "enum": [ + "true", + "false" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "COURT_JURISDICTION" + } + } + }, + "value": { + "type": "string" + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_BASIS" + } + } + }, + "value": { + "type": "string", + "enum": [ + "DELIVERY" + ] + } + } + } + }, + { + "contains": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "const": "SETTLEMENT_WINDOW" + } + } + }, + "value": { + "type": "string" + } + } + } + } + ] + } + } + } + } + } + } + } + } + ] + } + } + }, + "required": [ + "payments" + ] + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "catalog": { + "properties": { + "providers": { + "items": { + "properties": { + "fulfillments": { + "items": { + "properties": { + "stops": { + "allOf": [ + { + "contains": { + "type": "object", + "properties": { + "location": { + "type": "object", + "properties": { + "gps": { + "type": "string" + } + }, + "required": [ + "gps" + ] + }, + "type": { + "const": "START" + } + }, + "required": [ + "location", + "type" + ] + } + }, + { + "contains": { + "type": "object", + "properties": { + "location": { + "type": "object", + "properties": { + "gps": { + "type": "string" + } + }, + "required": [ + "gps" + ] + }, + "type": { + "const": "END" + } + }, + "required": [ + "location", + "type" + ] + } + } + ] + } + }, + "required": [ + "stops" + ] + } + } + } + } + } + } + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_select.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_select.json new file mode 100644 index 0000000..9ac80b7 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_select.json @@ -0,0 +1,230 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_select", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_select.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "$ref": "./on_init.json#/allOf/2" + }, + { + "$ref": "./confirm.json#/allOf/5" + }, + { + "$ref": "./on_init.json#/allOf/5" + }, + { + "allOf": [ + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "minItems": 1, + "items": { + "required": ["id"] + } + } + }, + "required": ["fulfillments"] + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "state": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "RIDE_ASSIGNED", + "RIDE_ENROUTE_PICKUP", + "RIDE_ARRIVED_PICKUP", + "RIDE_STARTED", + "RIDE_ENDED", + "RIDE_CANCELLED" + ] + } + }, + "required": ["code"] + } + } + } + } + } + } + } + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "minItems": 1, + "items": { + "properties": { + "stops": { + "items": { + "properties": { + "authorization": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["OTP"] + }, + "token": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + }, + "required": ["type", "token"] + } + } + } + } + } + } + } + } + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "items": { + "properties": { + "stops": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "properties": { + "location": { + "type": "object", + "properties": { + "gps": { "type": "string" } + }, + "required": ["gps"] + }, + "type": { + "enum": ["START", "END"] + } + }, + "required": ["location", "type"] + } + } + }, + "required": ["stops"] + } + } + } + } + } + } + } + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "vehicle": { + "properties": { + "category": { + "type": "string", + "enum": ["AUTO_RICKSHAW", "CAB"] + } + }, + "required": ["category"] + } + }, + "required": ["vehicle"] + } + } + }, + "required": ["fulfillments"] + } + } + } + } + } + ] + }, + { + "$ref": "./on_init.json#/allOf/7" + }, + { + "properties": { + "message": { + "properties": { + "order": { + "properties": { + "fulfillments": { + "type": "array", + "items": { + "allOf": [ + { + "not": { + "required": ["agent"] + } + } + ] + } + } + } + } + } + } + } + }, + { + "$ref": "./on_init.json#/allOf/8" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_status.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_status.json new file mode 100644 index 0000000..3ac9a6c --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_status.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_status", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_status.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_support.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_support.json new file mode 100644 index 0000000..5884c85 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_support.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_support", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_support.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_track.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_track.json new file mode 100644 index 0000000..7796a66 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_track.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_track", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_track.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_update.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_update.json new file mode 100644 index 0000000..487def2 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/on_update.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "on_update", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/on_update.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/rating.json b/config/onix/schemas/ondc_trv10/v2.0.0/rating.json new file mode 100644 index 0000000..5cfbc74 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/rating.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "rating", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/rating.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/search.json b/config/onix/schemas/ondc_trv10/v2.0.0/search.json new file mode 100644 index 0000000..f661f8e --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/search.json @@ -0,0 +1,146 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "search", + "allOf": [ + { "$ref": "../../core/v1.1.0/search.json#" } + ], + "type": "object", + "properties": { + "context": { + "type": "object", + "allOf": [ + { + "properties": { + "action": { + "type": "string" + }, + "location": { + "type": "object", + "properties": { + "city": { + "type": "object", + "properties": { + "code": { "type": "string" } + }, + "required": ["code"] + }, + "country": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["IND"] + } + }, + "required": ["code"] + } + }, + "required": ["city", "country"] + }, + "bap_id": { + "type": "string" + }, + "bpp_id": { + "type": "string" + }, + "ttl": { + "type": "string", + "format": "duration" + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "location", + "domain", + "action", + "message_id", + "transaction_id", + "timestamp", + "bap_id", + "bap_uri", + "ttl" + ] + } + ] + }, + "message": { + "type": "object", + "properties": { + "intent": { + "allOf": [ + { + "type": "object", + "properties": { + "payment": { + "type": "object", + "properties": { + "collected_by": { + "type": "string", + "enum": ["BPP", "BAP"] + }, + "tags": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "descriptor": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["SETTLEMENT_TERMS", "BUYER_FINDER_FEES"] + } + } + } + }, + "required": ["descriptor"] + } + } + }, + "required": ["collected_by"] + }, + "fulfillment": { + "type": "object", + "properties": { + "stops": { + "type": "array", + "items": { + "type": "object", + "properties": { + "location": { + "type": "object", + "properties": { + "gps": { "type": "string" } + }, + "required": ["gps"] + }, + "type": { + "type": "string", + "enum": ["START", "END"] + } + }, + "required": ["location", "type"] + }, + "minItems": 2 + } + }, + "required": ["stops"] + } + }, + "required": ["payment", "fulfillment"] + } + ] + } + }, + "required": ["intent"] + } + }, + "required": ["context", "message"] + } + \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/select.json b/config/onix/schemas/ondc_trv10/v2.0.0/select.json new file mode 100644 index 0000000..2d22396 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/select.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "select", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/select.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "$ref": "./init.json#/allOf/2" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/status.json b/config/onix/schemas/ondc_trv10/v2.0.0/status.json new file mode 100644 index 0000000..a9085d2 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/status.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "status", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/status.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "properties": { + "message": { + "type": "object", + "properties": { + "order_id": { + "type": "string" + } + }, + "required": ["order_id"] + } + } + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/support.json b/config/onix/schemas/ondc_trv10/v2.0.0/support.json new file mode 100644 index 0000000..de4dbda --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/support.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "support", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/support.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/track.json b/config/onix/schemas/ondc_trv10/v2.0.0/track.json new file mode 100644 index 0000000..242cec1 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/track.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "track", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/track.json#" + }, + { + "$ref": "./init.json#/allOf/1" + } + ] +} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/update.json b/config/onix/schemas/ondc_trv10/v2.0.0/update.json new file mode 100644 index 0000000..d197cc7 --- /dev/null +++ b/config/onix/schemas/ondc_trv10/v2.0.0/update.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "update", + "type": "object", + "allOf": [ + { + "$ref": "../../core/v1.1.0/update.json#" + }, + { + "$ref": "./init.json#/allOf/1" + }, + { + "properties": { + "message": { + "type": "object", + "properties": { + "order": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": ["id"] + }, + "update_target": { + "type": "string", + "pattern": "^[^,]+(,[^,]+)*$" + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/config/sellerData.yaml b/config/sellerData.yaml new file mode 100644 index 0000000..9a96fdf --- /dev/null +++ b/config/sellerData.yaml @@ -0,0 +1,69 @@ +- id: pooja-stores + descriptor: + name: Pooja Stores + locations: + - id: koramangala-4th-block-location + gps: "12.9349377,77.6055586" + categories: + - id: fresh_fruits + descriptor: + name: Fresh Fruits + - id: beverages + descriptor: + name: Beverages + items: + - id: item_2 + descriptor: + name: Green Apples Organic + images: + - url: "https://mock_bpp.com/images/item_2.jpg" + category_id: fresh_fruits + location_id: koramangala-4th-block-location + price: + currency: INR + value: "170" + matched: true + - id: item_1 + descriptor: + name: Red Apples + images: + - url: "https://mock_bpp.com/images/item_1.jpg" + category_id: fresh_fruits + location_id: koramangala-4th-block-location + price: + currency: INR + value: "90" + related: true + - id: item_7 + descriptor: + name: Green Apple Juice + images: + - url: "https://mock_bpp.com/images/item_7.jpg" + category_id: beverages + location_id: koramangala-4th-block-location + price: + currency: INR + value: "70" + matched: true +- id: food-mall + descriptor: + name: Food Mall + locations: + - id: food-mall-location + gps: "12.9349377,77.6055586" + categories: + - id: fresh-food + descriptor: + name: Fresh food + items: + - id: item_1_1 + descriptor: + name: Green Apple Salad + images: + - url: "https://mock_bpp.com/images/item_1_1.jpg" + category_id: fresh-food + location_id: food-mall-location + price: + currency: INR + value: "200" + matched: true diff --git a/core/module/client/registery.go b/core/module/client/registery.go new file mode 100644 index 0000000..2a688c6 --- /dev/null +++ b/core/module/client/registery.go @@ -0,0 +1,101 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/beckn/beckn-onix/pkg/model" + "github.com/hashicorp/go-retryablehttp" +) + +// Config struct to hold configuration parameters. +type Config struct { + RegisteryURL string + RetryMax int + RetryWaitMin time.Duration + RetryWaitMax time.Duration +} + +// RegisteryClient encapsulates the logic for calling the subscribe and lookup endpoints. +type RegisteryClient struct { + Config *Config + Client *retryablehttp.Client // Retryable HTTP Client +} + +// NewRegisteryClient creates a new instance of Client. +func NewRegisteryClient(config *Config) *RegisteryClient { + retryClient := retryablehttp.NewClient() + + return &RegisteryClient{Config: config, Client: retryClient} +} + +// Subscribe calls the /subscribe endpoint with retry. +func (c *RegisteryClient) Subscribe(ctx context.Context, subscription *model.Subscription) error { + subscribeURL := fmt.Sprintf("%s/subscribe", c.Config.RegisteryURL) + + jsonData, err := json.Marshal(subscription) + if err != nil { + return fmt.Errorf("failed to marshal subscription data: %w", err) + } + + req, err := retryablehttp.NewRequest("POST", subscribeURL, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := c.Client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request with retry: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("subscribe request failed with status: %s", resp.Status) + } + return nil +} + +// Lookup calls the /lookup endpoint with retry and returns a slice of Subscription. +func (c *RegisteryClient) Lookup(ctx context.Context, subscription *model.Subscription) ([]model.Subscription, error) { + lookupURL := fmt.Sprintf("%s/lookUp", c.Config.RegisteryURL) + + jsonData, err := json.Marshal(subscription) + if err != nil { + return nil, fmt.Errorf("failed to marshal subscription data: %w", err) + } + + req, err := retryablehttp.NewRequest("POST", lookupURL, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := c.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request with retry: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("lookup request failed with status: %s", resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + var results []model.Subscription + err = json.Unmarshal(body, &results) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response body: %w", err) + } + + return results, nil +} diff --git a/core/module/handler/config.go b/core/module/handler/config.go new file mode 100644 index 0000000..a33aa21 --- /dev/null +++ b/core/module/handler/config.go @@ -0,0 +1,117 @@ +package handler + +import ( + "context" + "fmt" + "net/http" + + "github.com/beckn/beckn-onix/pkg/model" + "github.com/beckn/beckn-onix/pkg/plugin" + "github.com/beckn/beckn-onix/pkg/plugin/definition" +) + +// PluginManager defines the methods required for managing plugins in stdHandler. +type PluginManager interface { + Middleware(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) + SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error) + Validator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) + Router(ctx context.Context, cfg *plugin.Config) (definition.Router, error) + Publisher(ctx context.Context, cfg *plugin.Config) (definition.Publisher, error) + Signer(ctx context.Context, cfg *plugin.Config) (definition.Signer, error) + Step(ctx context.Context, cfg *plugin.Config) (definition.Step, error) + Cache(ctx context.Context, cfg *plugin.Config) (definition.Cache, error) + KeyManager(ctx context.Context, cache definition.Cache, rLookup definition.RegistryLookup, cfg *plugin.Config) (definition.KeyManager, error) + SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) +} + +// Provider represents a function that initializes an HTTP handler using a PluginManager. +type Provider func(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) + +// Type defines different handler types for processing requests. +type Type string + +const ( + // HandlerTypeStd represents the standard handler type used for general request processing. + HandlerTypeStd Type = "std" + + // HandlerTypeRegSub represents the registry subscriber handler type for handling registry subscription requests. + HandlerTypeRegSub Type = "regSub" + + // HandlerTypeNPSub represents the network participant subscriber handler type for handling network participant subscription requests. + HandlerTypeNPSub Type = "npSub" + + // HandlerTypeLookup represents the lookup handler type used for resolving service details. + HandlerTypeLookup Type = "lookUp" +) + +// pluginCfg holds the configuration for various plugins. +type pluginCfg struct { + SchemaValidator *plugin.Config `yaml:"schemaValidator,omitempty"` + SignValidator *plugin.Config `yaml:"signValidator,omitempty"` + Publisher *plugin.Config `yaml:"publisher,omitempty"` + Signer *plugin.Config `yaml:"signer,omitempty"` + Router *plugin.Config `yaml:"router,omitempty"` + Cache *plugin.Config `yaml:"cache,omitempty"` + KeyManager *plugin.Config `yaml:"keyManager,omitempty"` + Middleware []plugin.Config `yaml:"middleware,omitempty"` + Steps []plugin.Config +} + +// Config holds the configuration for request processing handlers. +type Config struct { + Plugins pluginCfg `yaml:"plugins"` + Steps []string + Type Type + RegistryURL string `yaml:"registryUrl"` + Role model.Role + SubscriberID string `yaml:"subscriberId"` + Trace map[string]bool +} + +// Step represents a named processing step. +type Step string + +const ( + // StepInitialize represents the initialization phase of the request processing pipeline. + StepInitialize Step = "initialize" + + // StepValidate represents the validation phase, where input data is checked for correctness. + StepValidate Step = "validate" + + // StepProcess represents the core processing phase of the request. + StepProcess Step = "process" + + // StepFinalize represents the finalization phase, where the response is prepared and sent. + StepFinalize Step = "finalize" +) + +// validSteps ensures only allowed step values are used. +var validSteps = map[Step]bool{ + StepInitialize: true, + StepValidate: true, + StepProcess: true, + StepFinalize: true, +} + +// UnmarshalYAML customizes YAML unmarshalling for Step to enforce valid values. +func (s *Step) UnmarshalYAML(unmarshal func(interface{}) error) error { + var stepName string + if err := unmarshal(&stepName); err != nil { + return err + } + + step := Step(stepName) + if !validSteps[step] { + return fmt.Errorf("invalid step: %s", stepName) + } + *s = step + return nil +} + +// DummyHandler is a basic HTTP handler that returns a fixed response. +func DummyHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Dummy Handler Response")) + }), nil +} diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go new file mode 100644 index 0000000..bc6786b --- /dev/null +++ b/core/module/handler/stdHandler.go @@ -0,0 +1,264 @@ +package handler + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/http/httputil" + "net/url" + + "github.com/beckn/beckn-onix/core/module/client" + "github.com/beckn/beckn-onix/pkg/log" + "github.com/beckn/beckn-onix/pkg/model" + "github.com/beckn/beckn-onix/pkg/plugin" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/response" +) + +// stdHandler orchestrates the execution of defined processing steps. +type stdHandler struct { + signer definition.Signer + steps []definition.Step + signValidator definition.Verifier + cache definition.Cache + km definition.KeyManager + schemaValidator definition.SchemaValidator + router definition.Router + publisher definition.Publisher + SubscriberID string + role model.Role +} + +// NewStdHandler initializes a new processor with plugins and steps. +func NewStdHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) { + h := &stdHandler{ + steps: []definition.Step{}, + SubscriberID: cfg.SubscriberID, + role: cfg.Role, + } + // Initialize plugins + if err := h.initPlugins(ctx, mgr, &cfg.Plugins, cfg.RegistryURL); err != nil { + return nil, fmt.Errorf("failed to initialize plugins: %w", err) + } + // Initialize steps + if err := h.initSteps(ctx, mgr, cfg); err != nil { + return nil, fmt.Errorf("failed to initialize steps: %w", err) + } + return h, nil +} + +// ServeHTTP processes an incoming HTTP request and executes defined processing steps. +func (h *stdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx, err := h.stepCtx(r, w.Header()) + if err != nil { + log.Errorf(r.Context(), err, "stepCtx(r):%v", err) + response.SendNack(r.Context(), w, err) + return + } + log.Request(r.Context(), r, ctx.Body) + + // Execute processing steps + for _, step := range h.steps { + if err := step.Run(ctx); err != nil { + log.Errorf(ctx, err, "%T.run(%v):%v", step, ctx, err) + response.SendNack(ctx, w, err) + return + } + } + // Restore request body before forwarding or publishing + r.Body = io.NopCloser(bytes.NewReader(ctx.Body)) + if ctx.Route == nil { + response.SendAck(w) + return + } + + // Handle routing based on the defined route type + route(ctx, r, w, h.publisher) +} + +// stepCtx creates a new StepContext for processing an HTTP request. +func (h *stdHandler) stepCtx(r *http.Request, rh http.Header) (*model.StepContext, error) { + var bodyBuffer bytes.Buffer + if _, err := io.Copy(&bodyBuffer, r.Body); err != nil { + return nil, model.NewBadReqErr(err) + } + r.Body.Close() + subID := h.subID(r.Context()) + if len(subID) == 0 { + return nil, model.NewBadReqErr(fmt.Errorf("subscriberID not set")) + } + return &model.StepContext{ + Context: r.Context(), + Request: r, + Body: bodyBuffer.Bytes(), + Role: h.role, + SubID: subID, + RespHeader: rh, + }, nil +} + +// subID retrieves the subscriber ID from the request context. +func (h *stdHandler) subID(ctx context.Context) string { + rSubID, ok := ctx.Value("subscriber_id").(string) + if ok { + return rSubID + } + return h.SubscriberID +} + +// route handles request forwarding or message publishing based on the routing type. +func route(ctx *model.StepContext, r *http.Request, w http.ResponseWriter, pb definition.Publisher) { + log.Debugf(ctx, "Routing to ctx.Route to %#v", ctx.Route) + switch ctx.Route.Type { + case "url": + log.Infof(ctx.Context, "Forwarding request to URL: %s", ctx.Route.URL) + proxy(r, w, ctx.Route.URL) + return + case "publisher": + if pb == nil { + err := fmt.Errorf("publisher plugin not configured") + log.Errorf(ctx.Context, err, "Invalid configuration:%v", err) + response.SendNack(ctx, w, err) + return + } + log.Infof(ctx.Context, "Publishing message to: %s", ctx.Route.Publisher) + if err := pb.Publish(ctx, ctx.Body); err != nil { + log.Errorf(ctx.Context, err, "Failed to publish message") + http.Error(w, "Error publishing message", http.StatusInternalServerError) + response.SendNack(ctx, w, err) + return + } + default: + err := fmt.Errorf("unknown route type: %s", ctx.Route.Type) + log.Errorf(ctx.Context, err, "Invalid configuration:%v", err) + response.SendNack(ctx, w, err) + return + } + response.SendAck(w) +} + +// proxy forwards the request to a target URL using a reverse proxy. +func proxy(r *http.Request, w http.ResponseWriter, target *url.URL) { + r.URL.Scheme = target.Scheme + r.URL.Host = target.Host + r.URL.Path = target.Path + + r.Header.Set("X-Forwarded-Host", r.Host) + proxy := httputil.NewSingleHostReverseProxy(target) + log.Infof(r.Context(), "Proxying request to: %s", target) + + proxy.ServeHTTP(w, r) +} + +// loadPlugin is a generic function to load and validate plugins. +func loadPlugin[T any](ctx context.Context, name string, cfg *plugin.Config, mgrFunc func(context.Context, *plugin.Config) (T, error)) (T, error) { + var zero T + if cfg == nil { + log.Debugf(ctx, "Skipping %s plugin: not configured", name) + return zero, nil + } + + plugin, err := mgrFunc(ctx, cfg) + if err != nil { + return zero, fmt.Errorf("failed to load %s plugin (%s): %w", name, cfg.ID, err) + } + + log.Debugf(ctx, "Loaded %s plugin: %s", name, cfg.ID) + return plugin, nil +} + +// loadKeyManager loads the KeyManager plugin using the provided PluginManager, cache, and registry URL. +func loadKeyManager(ctx context.Context, mgr PluginManager, cache definition.Cache, cfg *plugin.Config, regURL string) (definition.KeyManager, error) { + if cfg == nil { + log.Debug(ctx, "Skipping KeyManager plugin: not configured") + return nil, nil + } + if cache == nil { + return nil, fmt.Errorf("failed to load KeyManager plugin (%s): Cache plugin not configured", cfg.ID) + } + rClient := client.NewRegisteryClient(&client.Config{RegisteryURL: regURL}) + km, err := mgr.KeyManager(ctx, cache, rClient, cfg) + if err != nil { + return nil, fmt.Errorf("failed to load cache plugin (%s): %w", cfg.ID, err) + } + + log.Debugf(ctx, "Loaded Keymanager plugin: %s", cfg.ID) + return km, nil +} + +// initPlugins initializes required plugins for the processor. +func (h *stdHandler) initPlugins(ctx context.Context, mgr PluginManager, cfg *pluginCfg, regURL string) error { + var err error + if h.cache, err = loadPlugin(ctx, "Cache", cfg.Cache, mgr.Cache); err != nil { + return err + } + if h.km, err = loadKeyManager(ctx, mgr, h.cache, cfg.KeyManager, regURL); err != nil { + return err + } + if h.signValidator, err = loadPlugin(ctx, "SignValidator", cfg.SignValidator, mgr.SignValidator); err != nil { + return err + } + if h.schemaValidator, err = loadPlugin(ctx, "SchemaValidator", cfg.SchemaValidator, mgr.SchemaValidator); err != nil { + return err + } + if h.router, err = loadPlugin(ctx, "Router", cfg.Router, mgr.Router); err != nil { + return err + } + if h.publisher, err = loadPlugin(ctx, "Publisher", cfg.Publisher, mgr.Publisher); err != nil { + return err + } + if h.signer, err = loadPlugin(ctx, "Signer", cfg.Signer, mgr.Signer); err != nil { + return err + } + + log.Debugf(ctx, "All required plugins successfully loaded for stdHandler") + return nil +} + +// initSteps initializes and validates processing steps for the processor. +func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Config) error { + steps := make(map[string]definition.Step) + + // Load plugin-based steps + for _, c := range cfg.Plugins.Steps { + step, err := mgr.Step(ctx, &c) + if err != nil { + return fmt.Errorf("failed to initialize plugin step %s: %w", c.ID, err) + } + steps[c.ID] = step + } + + // Register processing steps + for _, step := range cfg.Steps { + var s definition.Step + var err error + + switch step { + case "sign": + s, err = newSignStep(h.signer, h.km) + case "validateSign": + s, err = newValidateSignStep(h.signValidator, h.km) + case "validateSchema": + s, err = newValidateSchemaStep(h.schemaValidator) + case "addRoute": + s, err = newRouteStep(h.router) + case "broadcast": + s = &broadcastStep{} + default: + if customStep, exists := steps[step]; exists { + s = customStep + } else { + return fmt.Errorf("unrecognized step: %s", step) + } + } + + if err != nil { + return err + } + h.steps = append(h.steps, s) + } + log.Infof(ctx, "Processor steps initialized: %v", cfg.Steps) + return nil +} diff --git a/core/module/handler/step.go b/core/module/handler/step.go new file mode 100644 index 0000000..448a937 --- /dev/null +++ b/core/module/handler/step.go @@ -0,0 +1,181 @@ +package handler + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/beckn/beckn-onix/pkg/log" + "github.com/beckn/beckn-onix/pkg/model" + "github.com/beckn/beckn-onix/pkg/plugin/definition" +) + +// signStep represents the signing step in the processing pipeline. +type signStep struct { + signer definition.Signer + km definition.KeyManager +} + +// newSignStep initializes and returns a new signing step. +func newSignStep(signer definition.Signer, km definition.KeyManager) (definition.Step, error) { + if signer == nil { + return nil, fmt.Errorf("invalid config: Signer plugin not configured") + } + if km == nil { + return nil, fmt.Errorf("invalid config: KeyManager plugin not configured") + } + + return &signStep{signer: signer, km: km}, nil +} + +// Run executes the signing step. +func (s *signStep) Run(ctx *model.StepContext) error { + keyID, key, err := s.km.SigningPrivateKey(ctx, ctx.SubID) + if err != nil { + return fmt.Errorf("failed to get signing key: %w", err) + } + createdAt := time.Now().Unix() + validTill := time.Now().Add(5 * time.Minute).Unix() + sign, err := s.signer.Sign(ctx, ctx.Body, key, createdAt, validTill) + if err != nil { + return fmt.Errorf("failed to sign request: %w", err) + } + authHeader := fmt.Sprintf("Signature keyId=\"%s|%s|ed25519\",algorithm=\"ed25519\",created=\"%d\",expires=\"%d\",headers=\"(created) (expires) digest\",signature=\"%s\"", ctx.SubID, keyID, createdAt, validTill, sign) + header := model.AuthHeaderSubscriber + if ctx.Role == model.RoleGateway { + header = model.AuthHeaderGateway + } + ctx.Request.Header.Set(header, authHeader) + return nil +} + +// validateSignStep represents the signature validation step. +type validateSignStep struct { + validator definition.Verifier + km definition.KeyManager +} + +// newValidateSignStep initializes and returns a new validate sign step. +func newValidateSignStep(signValidator definition.Verifier, km definition.KeyManager) (definition.Step, error) { + if signValidator == nil { + return nil, fmt.Errorf("invalid config: SignValidator plugin not configured") + } + if km == nil { + return nil, fmt.Errorf("invalid config: KeyManager plugin not configured") + } + return &validateSignStep{validator: signValidator, km: km}, nil +} + +// Run executes the validation step. +func (s *validateSignStep) Run(ctx *model.StepContext) error { + unauthHeader := fmt.Sprintf("Signature realm=\"%s\",headers=\"(created) (expires) digest\"", ctx.SubID) + headerValue := ctx.Request.Header.Get(model.AuthHeaderGateway) + if len(headerValue) != 0 { + if err := s.validate(ctx, headerValue); err != nil { + ctx.RespHeader.Set(model.UnaAuthorizedHeaderGateway, unauthHeader) + return model.NewSignValidationErrf("failed to validate %s: %w", model.AuthHeaderGateway, err) + } + } + headerValue = ctx.Request.Header.Get(model.AuthHeaderSubscriber) + if len(headerValue) == 0 { + ctx.RespHeader.Set(model.UnaAuthorizedHeaderSubscriber, unauthHeader) + return model.NewSignValidationErrf("%s missing", model.UnaAuthorizedHeaderSubscriber) + } + if err := s.validate(ctx, headerValue); err != nil { + ctx.RespHeader.Set(model.UnaAuthorizedHeaderSubscriber, unauthHeader) + return model.NewSignValidationErrf("failed to validate %s: %w", model.AuthHeaderSubscriber, err) + } + return nil +} + +// validate checks the validity of the provided signature header. +func (s *validateSignStep) validate(ctx *model.StepContext, value string) error { + headerParts := strings.Split(value, "|") + ids := strings.Split(headerParts[0], "\"") + if len(ids) < 2 || len(headerParts) < 3 { + return fmt.Errorf("malformed sign header") + } + subID := ids[1] + keyID := headerParts[1] + key, err := s.km.SigningPublicKey(ctx, subID, keyID) + if err != nil { + return fmt.Errorf("failed to get validation key: %w", err) + } + if _, err := s.validator.Verify(ctx, ctx.Body, []byte(value), key); err != nil { + return fmt.Errorf("sign validation failed: %w", err) + } + return nil +} + +// validateSchemaStep represents the schema validation step. +type validateSchemaStep struct { + validator definition.SchemaValidator +} + +// newValidateSchemaStep creates and returns the validateSchema step after validation +func newValidateSchemaStep(schemaValidator definition.SchemaValidator) (definition.Step, error) { + if schemaValidator == nil { + return nil, fmt.Errorf("invalid config: SchemaValidator plugin not configured") + } + log.Debug(context.Background(), "adding schema validator") + return &validateSchemaStep{validator: schemaValidator}, nil +} + +// Run executes the schema validation step. +func (s *validateSchemaStep) Run(ctx *model.StepContext) error { + if err := s.validator.Validate(ctx, ctx.Request.URL, ctx.Body); err != nil { + return fmt.Errorf("schema validation failed: %w", err) + } + return nil +} + +// addRouteStep represents the route determination step. +type addRouteStep struct { + router definition.Router +} + +// newRouteStep initializes and returns a new routing step. +func newRouteStep(router definition.Router) (definition.Step, error) { + if router == nil { + return nil, fmt.Errorf("invalid config: Router plugin not configured") + } + return &addRouteStep{router: router}, nil +} + +// Run executes the routing step. +func (s *addRouteStep) Run(ctx *model.StepContext) error { + route, err := s.router.Route(ctx, ctx.Request.URL, ctx.Body) + if err != nil { + return fmt.Errorf("failed to determine route: %w", err) + } + log.Debugf(ctx, "Routing to %#v", route) + ctx.Route = route + + log.Debugf(ctx, "ctx.Route to %#v", ctx.Route) + return nil +} + +// broadcastStep is a stub for broadcasting. +type broadcastStep struct{} + +// Run is a placeholder for future implementation. +func (b *broadcastStep) Run(ctx *model.StepContext) error { + // TODO: Implement broadcast logic if needed + return nil +} + +// subscribeStep is a stub for subscription handling. +type subscribeStep struct{} + +// Run is a placeholder for future implementation. +func (s *subscribeStep) Run(ctx *model.StepContext) error { + // TODO: Implement subscription logic if needed + return nil +} + +// tracingStep wraps a Step with OpenTelemetry tracing +type tracingStep struct { + step definition.Step + name string +} diff --git a/core/module/module.go b/core/module/module.go new file mode 100644 index 0000000..9313712 --- /dev/null +++ b/core/module/module.go @@ -0,0 +1,84 @@ +package module + +import ( + "context" + "fmt" + "net/http" + + "github.com/beckn/beckn-onix/core/module/handler" + "github.com/beckn/beckn-onix/pkg/log" +) + +// Config represents the configuration for a module. +type Config struct { + Name string `yaml:"name"` + Path string `yaml:"path"` + Handler handler.Config +} + +// handlerProviders maintains a mapping of handler types to their respective providers. +var handlerProviders = map[handler.Type]handler.Provider{ + handler.HandlerTypeStd: handler.NewStdHandler, +} + +// GetDummyHandlerProviders returns a dummy handler provider mapping for testing purposes. +func GetDummyHandlerProviders() map[handler.Type]handler.Provider { + return map[handler.Type]handler.Provider{ + handler.HandlerTypeStd: handler.DummyHandler, + } +} + +// getHandlerProviders is a function to retrieve handler providers, which can be overridden for testing. +var getHandlerProviders = func() map[handler.Type]handler.Provider { + return handlerProviders +} + +// Register initializes and registers handlers based on the provided configuration. +// It iterates over the module configurations, retrieves appropriate handler providers, +// and registers the handlers with the HTTP multiplexer. +func Register(ctx context.Context, mCfgs []Config, mux *http.ServeMux, mgr handler.PluginManager) error { + log.Debugf(ctx, "Registering modules with config: %#v", mCfgs) + + providers := getHandlerProviders() + // Iterate over the handlers in the configuration. + for _, c := range mCfgs { + rmp, ok := providers[c.Handler.Type] + if !ok { + return fmt.Errorf("invalid module : %s", c.Name) + } + h, err := rmp(ctx, mgr, &c.Handler) + if err != nil { + return fmt.Errorf("%s : %w", c.Name, err) + } + h, err = addMiddleware(ctx, mgr, h, &c.Handler) + if err != nil { + return fmt.Errorf("failed to add middleware: %w", err) + + } + log.Debugf(ctx, "Registering handler %s, of type %s @ %s", c.Name, c.Handler.Type, c.Path) + mux.Handle(c.Path, h) + } + return nil +} + +// addMiddleware applies middleware plugins to the provided handler in reverse order. +// It retrieves middleware instances from the plugin manager and chains them to the handler. +func addMiddleware(ctx context.Context, mgr handler.PluginManager, handler http.Handler, hCfg *handler.Config) (http.Handler, error) { + mws := hCfg.Plugins.Middleware + log.Debugf(ctx, "Applying %d middleware(s) to the handler", len(mws)) + // Apply the middleware in reverse order. + for i := len(mws) - 1; i >= 0; i-- { + log.Debugf(ctx, "Loading middleware: %s", mws[i].ID) + mw, err := mgr.Middleware(ctx, &mws[i]) + if err != nil { + log.Errorf(ctx, err, "Failed to load middleware %s: %v", mws[i].ID, err) + return nil, fmt.Errorf("failed to load middleware %s: %w", mws[i].ID, err) + } + // Apply the middleware to the handler. + handler = mw(handler) + log.Debugf(ctx, "Applied middleware: %s", mws[i].ID) + } + + log.Debugf(ctx, "Middleware chain setup completed") + return handler, nil +} diff --git a/go.mod b/go.mod index c4129fc..1a69f5f 100644 --- a/go.mod +++ b/go.mod @@ -7,41 +7,28 @@ toolchain go1.23.7 require golang.org/x/crypto v0.36.0 require ( - github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 - cloud.google.com/go v0.119.0 // indirect - cloud.google.com/go/auth v0.15.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.4.1 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/s2a-go v0.1.9 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.11.0 // indirect - google.golang.org/api v0.224.0 // indirect - google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e // indirect - google.golang.org/grpc v1.71.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect ) require ( - cloud.google.com/go/pubsub v1.48.0 + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect +) + +require ( + github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/rs/zerolog v1.34.0 golang.org/x/sys v0.31.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 2bc3958..128da49 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,8 @@ -github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY= -github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE= -cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08= -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= -cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= -cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM= -cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM= -cloud.google.com/go/pubsub v1.48.0 h1:ntFpQVrr10Wj/GXSOpxGmexGynldv/bFp25H0jy8aOs= -cloud.google.com/go/pubsub v1.48.0/go.mod h1:AAtyjyIT/+zaY1ERKFJbefOvkUxRDNp3nD6TdfdqUZk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -30,47 +10,39 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= -github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g= -github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY= +github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -79,84 +51,19 @@ go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.224.0 h1:Ir4UPtDsNiwIOHdExr3fAj4xZ42QjK7uQte3lORLJwU= -google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e h1:YA5lmSs3zc/5w+xsRcHqpETkaYyK63ivEPzNTcUUlSA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000..e73f517 --- /dev/null +++ b/pkg/log/log.go @@ -0,0 +1,322 @@ +package log + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "strconv" + "sync" + "time" + + "github.com/rs/zerolog" + "gopkg.in/natefinch/lumberjack.v2" +) + +// Error definitions for logging configuration. +var ( + ErrInvalidLogLevel = errors.New("invalid log level") + ErrLogDestinationNil = errors.New("log Destinations cant be empty") + ErrMissingFilePath = errors.New("file path missing in destination config for file logging") +) + +// DestinationType represents the type of logging destination. +type DestinationType string + +// Supported logging destinations. +const ( + Stdout DestinationType = "stdout" + File DestinationType = "file" +) + +// Destination defines a log output destination. +type Destination struct { + Type DestinationType `yaml:"type"` // Specifies destination type + Config map[string]string `yaml:"config"` // holds destination-specific configuration. +} + +// Level represents logging levels. +type Level string + +// Supported log levels. +const ( + DebugLevel Level = "debug" + InfoLevel Level = "info" + WarnLevel Level = "warn" + ErrorLevel Level = "error" + FatalLevel Level = "fatal" + PanicLevel Level = "panic" +) + +// Mapping of Level to zerolog.Level. +var logLevels = map[Level]zerolog.Level{ + DebugLevel: zerolog.DebugLevel, + InfoLevel: zerolog.InfoLevel, + WarnLevel: zerolog.WarnLevel, + ErrorLevel: zerolog.ErrorLevel, + FatalLevel: zerolog.FatalLevel, + PanicLevel: zerolog.PanicLevel, +} + +// Config represents the logging configuration. +type Config struct { + Level Level `yaml:"level"` //Logging Level + Destinations []Destination `yaml:"destinations"` // List of log destinations + ContextKeys []string `yaml:"contextKeys"` // List of context keys to extract +} + +// Logger Instance +var ( + logger zerolog.Logger + once sync.Once + cfg Config +) + +// init initializes the logger with default configuration. +func init() { + logger, _ = getLogger(defaultConfig) +} + +// InitLogger initializes the logger with the provided configuration. +// +// It ensures that the logger is initialized only once using sync.Once. +// Returns an error if the configuration is invalid. +func InitLogger(c Config) error { + var err error + once.Do(func() { // makes it singleton + err = c.validate() + if err != nil { + return + } + logger, err = getLogger(c) + if err != nil { + return + } + + }) + return err +} + +// getLogger creates and configures a new logger based on the given configuration. +// Returns an initialized zerolog.Logger or an error if configuration is invalid. +func getLogger(config Config) (zerolog.Logger, error) { + var newLogger zerolog.Logger + + // Multiwriter for multiple log destinations + var writers []io.Writer + for _, dest := range config.Destinations { + switch dest.Type { + case Stdout: + writers = append(writers, os.Stdout) + case File: + filePath := dest.Config["path"] + + // File rotation + lumberjackLogger := &lumberjack.Logger{ + Filename: filePath, + } + + setConfigValue := func(key string, target *int) { + if valStr, ok := dest.Config[key]; ok { + if val, err := strconv.Atoi(valStr); err == nil { + *target = val + } + } + } + + setConfigValue("maxSize", &lumberjackLogger.MaxSize) + setConfigValue("maxBackups", &lumberjackLogger.MaxBackups) + setConfigValue("maxAge", &lumberjackLogger.MaxAge) + + if compress, ok := dest.Config["compress"]; ok { + lumberjackLogger.Compress = compress == "true" + } + writers = append(writers, lumberjackLogger) + } + } + + multiwriter := io.MultiWriter(writers...) + + newLogger = zerolog.New(multiwriter). + Level(logLevels[config.Level]). + With(). + Timestamp(). + Caller(). + Logger() + + // Replace the cfg with given config + cfg = config + + return newLogger, nil + +} + +// validate checks if the provided logging configuration is valid. +// It ensures that a valid log level is provided and that at least one +// destination is specified. Returns an error if validation fails +func (config *Config) validate() error { + // Log Level is valid + if _, exists := logLevels[config.Level]; !exists { + return ErrInvalidLogLevel + } + + // Log Destinations is not empty + if len(config.Destinations) == 0 { + return ErrLogDestinationNil + } + + // File path exists in destination config for File type destination + for _, dest := range config.Destinations { + switch dest.Type { + case Stdout: + + case File: + if _, exists := dest.Config["path"]; !exists { + return ErrMissingFilePath + } + // Validate lumberjack config if present + for _, key := range []string{"maxSize", "maxBackups", "maxAge"} { + if valStr, ok := dest.Config[key]; ok { + if _, err := strconv.Atoi(valStr); err != nil { + return fmt.Errorf("invalid %s: %w", key, err) + } + } + } + default: + return fmt.Errorf("Invalid destination type '%s'", dest.Type) + } + } + return nil +} + +// Default Config +var defaultConfig = Config{ + Level: InfoLevel, + Destinations: []Destination{ + {Type: Stdout}, + }, + ContextKeys: []string{}, +} + +// Debug logs a debug-level message. +func Debug(ctx context.Context, msg string) { + logEvent(ctx, zerolog.DebugLevel, msg, nil) +} + +// Debugf logs a formatted debug-level message. +func Debugf(ctx context.Context, format string, v ...any) { + msg := fmt.Sprintf(format, v...) + logEvent(ctx, zerolog.DebugLevel, msg, nil) +} + +// Info logs an info-level message. +func Info(ctx context.Context, msg string) { + logEvent(ctx, zerolog.InfoLevel, msg, nil) +} + +// Infof logs a formatted info-level message. +func Infof(ctx context.Context, format string, v ...any) { + msg := fmt.Sprintf(format, v...) + logEvent(ctx, zerolog.InfoLevel, msg, nil) +} + +// Warn logs a warning-level message. +func Warn(ctx context.Context, msg string) { + logEvent(ctx, zerolog.WarnLevel, msg, nil) +} + +// Warnf logs a formatted warning-level message. +func Warnf(ctx context.Context, format string, v ...any) { + msg := fmt.Sprintf(format, v...) + logEvent(ctx, zerolog.WarnLevel, msg, nil) +} + +// Error logs an error-level message. +func Error(ctx context.Context, err error, msg string) { + logEvent(ctx, zerolog.ErrorLevel, msg, err) +} + +// Errorf logs a formatted error-level message. +func Errorf(ctx context.Context, err error, format string, v ...any) { + msg := fmt.Sprintf(format, v...) + logEvent(ctx, zerolog.ErrorLevel, msg, err) +} + +// Fatal logs a fatal-level message and exits the application. +func Fatal(ctx context.Context, err error, msg string) { + logEvent(ctx, zerolog.FatalLevel, msg, err) +} + +// Fatalf logs a formatted fatal-level message and exits the application. +func Fatalf(ctx context.Context, err error, format string, v ...any) { + msg := fmt.Sprintf(format, v...) + logEvent(ctx, zerolog.FatalLevel, msg, err) +} + +// Panic logs a panic-level message. +func Panic(ctx context.Context, err error, msg string) { + logEvent(ctx, zerolog.PanicLevel, msg, err) +} + +// Panicf logs a formatted panic-level message. +func Panicf(ctx context.Context, err error, format string, v ...any) { + msg := fmt.Sprintf(format, v...) + logEvent(ctx, zerolog.PanicLevel, msg, err) +} + +// Request logs an HTTP request. +func Request(ctx context.Context, r *http.Request, body []byte) { + event := logger.Info() + // Iterate through headers and log them + for name, values := range r.Header { + for _, value := range values { + event = event.Str(name, value) + } + } + addCtx(ctx, event) + + event.Str("method", r.Method). + Str("url", r.URL.String()). + Str("body", string(body)). + Str("remoteAddr", r.RemoteAddr). + Msg("HTTP Request") +} + +// Response logs an HTTP response. +func Response(ctx context.Context, r *http.Request, statusCode int, responseTime time.Duration) { + event := logger.Info() + + addCtx(ctx, event) + + event.Str("method", r.Method). + Str("url", r.URL.String()). + Int("statusCode", statusCode). + Dur("responseTime", responseTime). + Msg("HTTP Response") +} + +// logEvent logs messages at the specified level with optional error details. +func logEvent(ctx context.Context, level zerolog.Level, msg string, err error) { + event := logger.WithLevel(level) + + // Attach error if provided + if err != nil { + event = event.Err(err) + } + + // Add context fields + addCtx(ctx, event) + + event.Msg(msg) +} + +// addCtx adds context-specific fields to log events. +func addCtx(ctx context.Context, event *zerolog.Event) { + for _, key := range cfg.ContextKeys { + if val, ok := ctx.Value(key).(string); ok { + event.Any(key, val) + } + } +} diff --git a/pkg/model/error.go b/pkg/model/error.go new file mode 100644 index 0000000..2a59658 --- /dev/null +++ b/pkg/model/error.go @@ -0,0 +1,128 @@ +package model + +import ( + "fmt" + "net/http" + "strings" +) + +// Error represents an error response. +type Error struct { + Code string `json:"code"` + Paths string `json:"paths,omitempty"` + Message string `json:"message"` +} + +// Error implements the error interface for the Error struct. +func (e *Error) Error() string { + return fmt.Sprintf("Error: Code=%s, Path=%s, Message=%s", e.Code, e.Paths, e.Message) +} + +// SchemaValidationErr represents a collection of schema validation failures. +type SchemaValidationErr struct { + Errors []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.Paths, err.Message)) + } + return strings.Join(errorMessages, "; ") +} + +// BecknError converts SchemaValidationErr into a Beckn-compliant Error response. +func (e *SchemaValidationErr) BecknError() *Error { + if len(e.Errors) == 0 { + return &Error{ + Code: http.StatusText(http.StatusBadRequest), + Message: "Schema validation error.", + } + } + + // Collect all error paths and messages + var paths []string + var messages []string + for _, err := range e.Errors { + if err.Paths != "" { + paths = append(paths, err.Paths) + } + messages = append(messages, err.Message) + } + + return &Error{ + Code: http.StatusText(http.StatusBadRequest), + Paths: strings.Join(paths, ";"), + Message: strings.Join(messages, "; "), + } +} + +// SignValidationErr represents an error that occurs during signature validation. +type SignValidationErr struct { + error +} + +// NewSignValidationErrf creates a new SignValidationErr with a formatted message. +func NewSignValidationErrf(format string, a ...any) *SignValidationErr { + return &SignValidationErr{fmt.Errorf(format, a...)} +} + +// NewSignValidationErr creates a new SignValidationErr from an existing error. +func NewSignValidationErr(e error) *SignValidationErr { + return &SignValidationErr{e} +} + +// BecknError converts SignValidationErr into a Beckn-compliant Error response. +func (e *SignValidationErr) BecknError() *Error { + return &Error{ + Code: http.StatusText(http.StatusUnauthorized), + Message: "Signature Validation Error: " + e.Error(), + } +} + +// BadReqErr represents an error related to a bad request. +type BadReqErr struct { + error +} + +// NewBadReqErr creates a new BadReqErr from an existing error. +func NewBadReqErr(err error) *BadReqErr { + return &BadReqErr{err} +} + +// NewBadReqErrf creates a new BadReqErr with a formatted message. +func NewBadReqErrf(format string, a ...any) *BadReqErr { + return &BadReqErr{fmt.Errorf(format, a...)} +} + +// BecknError converts BadReqErr into a Beckn-compliant Error response. +func (e *BadReqErr) BecknError() *Error { + return &Error{ + Code: http.StatusText(http.StatusBadRequest), + Message: "BAD Request: " + e.Error(), + } +} + +// NotFoundErr represents an error for a missing resource or endpoint. +type NotFoundErr struct { + error +} + +// NewNotFoundErr creates a new NotFoundErr from an existing error. +func NewNotFoundErr(err error) *NotFoundErr { + return &NotFoundErr{err} +} + +// NewNotFoundErrf creates a new NotFoundErr with a formatted message. +func NewNotFoundErrf(format string, a ...any) *NotFoundErr { + return &NotFoundErr{fmt.Errorf(format, a...)} +} + +// BecknError converts NotFoundErr into a Beckn-compliant Error response. +func (e *NotFoundErr) BecknError() *Error { + return &Error{ + Code: http.StatusText(http.StatusNotFound), + Message: "Endpoint not found: " + e.Error(), + } +} diff --git a/pkg/model/model.go b/pkg/model/model.go new file mode 100644 index 0000000..38a68ab --- /dev/null +++ b/pkg/model/model.go @@ -0,0 +1,132 @@ +package model + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" +) + +// Subscriber represents a unique operational configuration of a trusted platform on a network. +type Subscriber struct { + SubscriberID string `json:"subscriber_id"` + URL string `json:"url" format:"uri"` + Type string `json:"type" enum:"BAP,BPP,BG"` + Domain string `json:"domain"` +} + +// Subscription represents subscription details of a network participant. +type Subscription struct { + Subscriber `json:",inline"` + KeyID string `json:"key_id" format:"uuid"` + SigningPublicKey string `json:"signing_public_key"` + EncrPublicKey string `json:"encr_public_key"` + ValidFrom time.Time `json:"valid_from" format:"date-time"` + ValidUntil time.Time `json:"valid_until" format:"date-time"` + Status string `json:"status" enum:"INITIATED,UNDER_SUBSCRIPTION,SUBSCRIBED,EXPIRED,UNSUBSCRIBED,INVALID_SSL"` + Created time.Time `json:"created" format:"date-time"` + Updated time.Time `json:"updated" format:"date-time"` + Nonce string +} + +// Authorization-related constants for headers. +const ( + AuthHeaderSubscriber string = "Authorization" + AuthHeaderGateway string = "X-Gateway-Authorization" + UnaAuthorizedHeaderSubscriber string = "WWW-Authenticate" + UnaAuthorizedHeaderGateway string = "Proxy-Authenticate" +) + +// MsgIDKey represents the key for the message ID. +const MsgIDKey = "message_id" + +// Role defines different roles in the network. +type Role string + +const ( + // RoleBAP represents a Buyer App Participant. + RoleBAP Role = "bap" + + // RoleBPP represents a Buyer Platform Participant. + RoleBPP Role = "bpp" + + // RoleGateway represents a Network Gateway. + RoleGateway Role = "gateway" + + // RoleRegistery represents a Registry Service. + RoleRegistery Role = "registery" +) + +// validRoles ensures only allowed values are accepted +var validRoles = map[Role]bool{ + RoleBAP: true, + RoleBPP: true, + RoleGateway: true, + RoleRegistery: true, +} + +// UnmarshalYAML implements custom YAML unmarshalling for Role to ensure only valid values are accepted. +func (r *Role) UnmarshalYAML(unmarshal func(interface{}) error) error { + var roleName string + if err := unmarshal(&roleName); err != nil { + return err + } + + role := Role(roleName) + if !validRoles[role] { + return fmt.Errorf("invalid Role: %s", roleName) + } + *r = role + return nil +} + +// Route represents a network route for message processing. +type Route struct { + Type string + URL *url.URL + Publisher string +} + +// StepContext holds context information for a request processing step. +type StepContext struct { + context.Context + Request *http.Request + Body []byte + Route *Route + SubID string + Role Role + RespHeader http.Header +} + +// WithContext updates the context in StepContext while keeping other fields unchanged. +func (ctx *StepContext) WithContext(newCtx context.Context) { + ctx.Context = newCtx // Update the existing context, keeping all other fields unchanged. +} + +// Status represents the status of an acknowledgment. +type Status string + +const ( + // StatusACK indicates a successful acknowledgment. + StatusACK Status = "ACK" + + // StatusNACK indicates a negative acknowledgment. + StatusNACK Status = "NACK" +) + +// Ack represents an acknowledgment response. +type Ack struct { + Status Status `json:"status"` // ACK or NACK +} + +// Message represents the message object in the response. +type Message struct { + Ack Ack `json:"ack"` + Error *Error `json:"error,omitempty"` +} + +// Response represents the main response structure. +type Response struct { + Message Message `json:"message"` +} diff --git a/pkg/plugin/config.go b/pkg/plugin/config.go new file mode 100644 index 0000000..c20170d --- /dev/null +++ b/pkg/plugin/config.go @@ -0,0 +1,21 @@ +package plugin + +type PublisherCfg struct { + ID string `yaml:"id"` + Config map[string]string `yaml:"config"` +} + +type ValidatorCfg struct { + ID string `yaml:"id"` + Config map[string]string `yaml:"config"` +} + +type Config struct { + ID string `yaml:"id"` + Config map[string]string `yaml:"config"` +} + +type ManagerConfig struct { + Root string `yaml:"root"` + RemoteRoot string `yaml:"remoteRoot"` +} diff --git a/pkg/plugin/definition/cache.go b/pkg/plugin/definition/cache.go new file mode 100644 index 0000000..488249f --- /dev/null +++ b/pkg/plugin/definition/cache.go @@ -0,0 +1,27 @@ +package definition + +import ( + "context" + "time" +) + +// Cache defines the general cache interface for caching plugins. +type Cache interface { + // Get retrieves a value from the cache based on the given key. + Get(ctx context.Context, key string) (string, error) + + // Set stores a value in the cache with the given key and TTL (time-to-live) in seconds. + Set(ctx context.Context, key, value string, ttl time.Duration) error + + // Delete removes a value from the cache based on the given key. + Delete(ctx context.Context, key string) error + + // Clear removes all values from the cache. + Clear(ctx context.Context) error +} + +// CacheProvider interface defines the contract for managing cache instances. +type CacheProvider interface { + // New initializes a new cache instance with the given configuration. + New(ctx context.Context, config map[string]string) (Cache, func() error, error) +} diff --git a/pkg/plugin/definition/keymanager.go b/pkg/plugin/definition/keymanager.go new file mode 100644 index 0000000..8d037a4 --- /dev/null +++ b/pkg/plugin/definition/keymanager.go @@ -0,0 +1,35 @@ +package definition + +import ( + "context" + + "github.com/beckn/beckn-onix/pkg/model" +) + +type Keyset struct { + UniqueKeyID string + SigningPrivate string + SigningPublic string + EncrPrivate string + EncrPublic string +} + +// KeyManager defines the interface for key management operations/methods. +type KeyManager interface { + GenerateKeyPairs() (*Keyset, error) + StorePrivateKeys(ctx context.Context, keyID string, keys *Keyset) error + SigningPrivateKey(ctx context.Context, keyID string) (string, string, error) + EncrPrivateKey(ctx context.Context, keyID string) (string, string, error) + SigningPublicKey(ctx context.Context, subscriberID, uniqueKeyID string) (string, error) + EncrPublicKey(ctx context.Context, subscriberID, uniqueKeyID string) (string, error) + DeletePrivateKeys(ctx context.Context, keyID string) error +} + +// KeyManagerProvider initializes a new signer instance. +type KeyManagerProvider interface { + New(context.Context, Cache, RegistryLookup, map[string]string) (KeyManager, func() error, error) +} + +type RegistryLookup interface { + Lookup(ctx context.Context, req *model.Subscription) ([]model.Subscription, error) +} diff --git a/pkg/plugin/definition/middleware.go b/pkg/plugin/definition/middleware.go new file mode 100644 index 0000000..7701ed5 --- /dev/null +++ b/pkg/plugin/definition/middleware.go @@ -0,0 +1,10 @@ +package definition + +import ( + "context" + "net/http" +) + +type MiddlewareProvider interface { + New(ctx context.Context, cfg map[string]string) (func(http.Handler) http.Handler, error) +} diff --git a/pkg/plugin/definition/router.go b/pkg/plugin/definition/router.go new file mode 100644 index 0000000..5e3f034 --- /dev/null +++ b/pkg/plugin/definition/router.go @@ -0,0 +1,16 @@ +package definition + +import ( + "context" + "net/url" + + "github.com/beckn/beckn-onix/pkg/model" +) + +type Router interface { + Route(ctx context.Context, url *url.URL, body []byte) (*model.Route, error) +} + +type RouterProvider interface { + New(ctx context.Context, cfg map[string]string) (Router, error) +} diff --git a/pkg/plugin/definition/schemaValidator.go b/pkg/plugin/definition/schemaValidator.go new file mode 100644 index 0000000..d12fbd9 --- /dev/null +++ b/pkg/plugin/definition/schemaValidator.go @@ -0,0 +1,16 @@ +package definition + +import ( + "context" + "net/url" +) + +// SchemaValidator interface for schema validation. +type SchemaValidator interface { + Validate(ctx context.Context, url *url.URL, reqBody []byte) error +} + +// SchemaValidatorProvider interface for creating validators. +type SchemaValidatorProvider interface { + New(ctx context.Context, config map[string]string) (SchemaValidator, func() error, error) +} diff --git a/pkg/plugin/definition/step.go b/pkg/plugin/definition/step.go new file mode 100644 index 0000000..627675a --- /dev/null +++ b/pkg/plugin/definition/step.go @@ -0,0 +1,15 @@ +package definition + +import ( + "context" + + "github.com/beckn/beckn-onix/pkg/model" +) + +type Step interface { + Run(ctx *model.StepContext) error +} + +type StepProvider interface { + New(context.Context, map[string]string) (Step, func(), error) +} diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 86a0b02..44f6bcf 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -1,171 +1,338 @@ package plugin import ( + "archive/zip" "context" "fmt" + "io" + "io/fs" + "net/http" + "os" "path/filepath" "plugin" "strings" + "time" + "github.com/beckn/beckn-onix/pkg/log" "github.com/beckn/beckn-onix/pkg/plugin/definition" ) -// Config represents the plugin manager configuration. -type Config struct { - Root string `yaml:"root"` - Signer PluginConfig `yaml:"signer"` - Verifier PluginConfig `yaml:"verifier"` - Decrypter PluginConfig `yaml:"decrypter"` - Encrypter PluginConfig `yaml:"encrypter"` - Publisher PluginConfig `yaml:"publisher"` -} - -// PluginConfig represents configuration details for a plugin. -type PluginConfig struct { - ID string `yaml:"id"` - Config map[string]string `yaml:"config"` -} - -// Manager handles dynamic plugin loading and management. type Manager struct { - sp definition.SignerProvider - vp definition.VerifierProvider - dp definition.DecrypterProvider - ep definition.EncrypterProvider - pb definition.PublisherProvider - cfg *Config + plugins map[string]*plugin.Plugin + closers []func() } -// 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 signer plugin. - sp, err := provider[definition.SignerProvider](cfg.Root, cfg.Signer.ID) - if err != nil { - return nil, fmt.Errorf("failed to load signer plugin: %w", err) - } - - // Load publisher plugin. - pb, err := provider[definition.PublisherProvider](cfg.Root, cfg.Publisher.ID) - if err != nil { - return nil, fmt.Errorf("failed to load publisher plugin: %w", err) - } - - // Load verifier plugin. - vp, err := provider[definition.VerifierProvider](cfg.Root, cfg.Verifier.ID) - if err != nil { - return nil, fmt.Errorf("failed to load Verifier plugin: %w", err) - } - - // Load decrypter plugin. - dp, err := provider[definition.DecrypterProvider](cfg.Root, cfg.Decrypter.ID) - if err != nil { - return nil, fmt.Errorf("failed to load Decrypter plugin: %w", err) - } - - // Load encryption plugin. - ep, err := provider[definition.EncrypterProvider](cfg.Root, cfg.Encrypter.ID) - if err != nil { - return nil, fmt.Errorf("failed to load encryption plugin: %w", err) - } - - return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, cfg: cfg}, nil +func validateMgrCfg(cfg *ManagerConfig) error { + return nil } -// provider loads a plugin dynamically and retrieves its provider instance. -func provider[T any](root, id string) (T, error) { +func NewManager(ctx context.Context, cfg *ManagerConfig) (*Manager, func(), error) { + if err := validateMgrCfg(cfg); err != nil { + return nil, nil, fmt.Errorf("Invalid config: %w", err) + } + log.Debugf(ctx, "RemoteRoot : %s", cfg.RemoteRoot) + if len(cfg.RemoteRoot) != 0 { + log.Debugf(ctx, "Unzipping files from : %s to : %s", cfg.RemoteRoot, cfg.Root) + if err := unzip(cfg.RemoteRoot, cfg.Root); err != nil { + return nil, nil, err + } + } + plugins, err := plugins(ctx, cfg) + if err != nil { + return nil, nil, err + } + + closers := []func(){} + return &Manager{plugins: plugins, closers: closers}, func() { + for _, closer := range closers { + closer() + } + }, nil +} + +func plugins(ctx context.Context, cfg *ManagerConfig) (map[string]*plugin.Plugin, error) { + plugins := make(map[string]*plugin.Plugin) + + err := filepath.WalkDir(cfg.Root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil // Skip directories + } + + if strings.HasSuffix(d.Name(), ".so") { + id := strings.TrimSuffix(d.Name(), ".so") // Extract plugin ID + + log.Debugf(ctx, "Loading plugin: %s", id) + start := time.Now() + p, err := plugin.Open(path) // Use the full path + if err != nil { + return fmt.Errorf("failed to open plugin %s: %w", id, err) + } + elapsed := time.Since(start) + plugins[id] = p + log.Debugf(ctx, "Loaded plugin: %s in %s", id, elapsed) + } + return nil + }) + + if err != nil { + return nil, err + } + + return plugins, nil +} + +func provider[T any](plugins map[string]*plugin.Plugin, id string) (T, error) { var zero T - if len(strings.TrimSpace(id)) == 0 { - return zero, nil + pgn, ok := plugins[id] + if !ok { + return zero, fmt.Errorf("plugin %s not found", id) } - - p, err := plugin.Open(pluginPath(root, id)) + provider, err := pgn.Lookup("Provider") if err != nil { - return zero, fmt.Errorf("failed to open plugin %s: %w", id, err) + return zero, fmt.Errorf("failed to lookup Provider for %s: %w", id, err) } + log.Debugf(context.Background(), "Provider type: %T\n", provider) - symbol, err := p.Lookup("Provider") - if err != nil { - return zero, fmt.Errorf("failed to find Provider symbol in plugin %s: %w", id, err) - } - - prov, ok := symbol.(*T) + pp, ok := provider.(T) if !ok { return zero, fmt.Errorf("failed to cast Provider for %s", id) } - - return *prov, nil + log.Debugf(context.Background(), "Casting successful for: %s", provider) + return pp, nil } -// pluginPath constructs the path to the plugin shared object file. -func pluginPath(root, id string) string { - return filepath.Join(root, id+".so") -} - -// Signer retrieves the signing plugin instance. -func (m *Manager) Signer(ctx context.Context) (definition.Signer, func() error, error) { - if m.sp == nil { - return nil, nil, fmt.Errorf("signing plugin provider not loaded") - } - - signer, close, err := m.sp.New(ctx, m.cfg.Signer.Config) +// GetPublisher returns a Publisher instance based on the provided configuration. +// It reuses the loaded provider. +func (m *Manager) Publisher(ctx context.Context, cfg *Config) (definition.Publisher, error) { + pp, err := provider[definition.PublisherProvider](m.plugins, cfg.ID) if err != nil { - return nil, nil, fmt.Errorf("failed to initialize signer: %w", err) + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) } - return signer, close, nil -} - -// Verifier retrieves the verification plugin instance. -func (m *Manager) Verifier(ctx context.Context) (definition.Verifier, func() error, error) { - if m.vp == nil { - return nil, nil, fmt.Errorf("Verifier plugin provider not loaded") - } - - Verifier, close, err := m.vp.New(ctx, m.cfg.Verifier.Config) + p, err := pp.New(ctx, cfg.Config) if err != nil { - return nil, nil, fmt.Errorf("failed to initialize Verifier: %w", err) + return nil, err } - return Verifier, close, nil + return p, nil } -// Decrypter retrieves the decryption plugin instance. -func (m *Manager) Decrypter(ctx context.Context) (definition.Decrypter, func() error, error) { - if m.dp == nil { - return nil, nil, fmt.Errorf("decrypter plugin provider not loaded") +func (m *Manager) addCloser(closer func()) { + if closer != nil { + m.closers = append(m.closers, closer) } +} - decrypter, close, err := m.dp.New(ctx, m.cfg.Decrypter.Config) +func (m *Manager) SchemaValidator(ctx context.Context, cfg *Config) (definition.SchemaValidator, error) { + vp, err := provider[definition.SchemaValidatorProvider](m.plugins, cfg.ID) if err != nil { - return nil, nil, fmt.Errorf("failed to initialize Decrypter: %w", err) + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) } - return decrypter, close, nil -} - -// Encrypter retrieves the encryption plugin instance. -func (m *Manager) Encrypter(ctx context.Context) (definition.Encrypter, func() error, error) { - if m.ep == nil { - return nil, nil, fmt.Errorf("encryption plugin provider not loaded") - } - - encrypter, close, err := m.ep.New(ctx, m.cfg.Encrypter.Config) + v, closer, err := vp.New(ctx, cfg.Config) if err != nil { - return nil, nil, fmt.Errorf("failed to initialize encrypter: %w", err) + return nil, err } - return encrypter, close, nil + if closer != nil { + m.addCloser(func() { + if err := closer(); err != nil { + panic(err) + } + }) + } + return v, nil } -// Publisher retrieves the publisher plugin instance. -func (m *Manager) Publisher(ctx context.Context) (definition.Publisher, error) { - if m.pb == nil { - return nil, fmt.Errorf("publisher plugin provider not loaded") - } - - publisher, err := m.pb.New(ctx, m.cfg.Publisher.Config) +func (m *Manager) Router(ctx context.Context, cfg *Config) (definition.Router, error) { + rp, err := provider[definition.RouterProvider](m.plugins, cfg.ID) if err != nil { - return nil, fmt.Errorf("failed to initialize publisher: %w", err) + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) } - return publisher, nil + return rp.New(ctx, cfg.Config) + +} + +func (m *Manager) Middleware(ctx context.Context, cfg *Config) (func(http.Handler) http.Handler, error) { + mwp, err := provider[definition.MiddlewareProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + return mwp.New(ctx, cfg.Config) +} + +func (m *Manager) Step(ctx context.Context, cfg *Config) (definition.Step, error) { + sp, err := provider[definition.StepProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + step, closer, error := sp.New(ctx, cfg.Config) + if closer != nil { + m.closers = append(m.closers, closer) + } + return step, error +} + +func (m *Manager) Cache(ctx context.Context, cfg *Config) (definition.Cache, error) { + cp, err := provider[definition.CacheProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + c, close, err := cp.New(ctx, cfg.Config) + if err != nil { + return nil, err + } + m.addCloser(func() { + if err := close(); err != nil { + panic(err) + } + }) + return c, nil +} + +func (m *Manager) Signer(ctx context.Context, cfg *Config) (definition.Signer, error) { + sp, err := provider[definition.SignerProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + s, closer, err := sp.New(ctx, cfg.Config) + if err != nil { + return nil, err + } + if closer != nil { + m.addCloser(func() { + if err := closer(); err != nil { + panic(err) + } + }) + } + return s, nil +} +func (m *Manager) Encryptor(ctx context.Context, cfg *Config) (definition.Encrypter, error) { + ep, err := provider[definition.EncrypterProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + encrypter, closer, err := ep.New(ctx, cfg.Config) + if err != nil { + return nil, err + } + if closer != nil { + m.addCloser(func() { + if err := closer(); err != nil { + panic(err) + } + }) + } + return encrypter, nil +} + +func (m *Manager) Decryptor(ctx context.Context, cfg *Config) (definition.Decrypter, error) { + dp, err := provider[definition.DecrypterProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + + decrypter, closer, err := dp.New(ctx, cfg.Config) + if err != nil { + return nil, err + } + + if closer != nil { + m.addCloser(func() { + if err := closer(); err != nil { + panic(err) + } + }) + } + + return decrypter, nil +} + +func (m *Manager) SignValidator(ctx context.Context, cfg *Config) (definition.Verifier, error) { + svp, err := provider[definition.VerifierProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + v, closer, err := svp.New(ctx, cfg.Config) + if err != nil { + return nil, err + } + if closer != nil { + m.addCloser(func() { + if err := closer(); err != nil { + panic(err) + } + }) + } + return v, nil +} + +// KeyManager returns a KeyManager instance based on the provided configuration. +// It reuses the loaded provider. +func (m *Manager) KeyManager(ctx context.Context, cache definition.Cache, rClient definition.RegistryLookup, cfg *Config) (definition.KeyManager, error) { + + kmp, err := provider[definition.KeyManagerProvider](m.plugins, cfg.ID) + if err != nil { + return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) + } + km, close, err := kmp.New(ctx, cache, rClient, cfg.Config) + if err != nil { + return nil, err + } + m.addCloser(func() { + if err := close(); err != nil { + panic(err) + } + }) + return km, nil +} + +// Validator implements handler.PluginManager. +func (m *Manager) Validator(ctx context.Context, cfg *Config) (definition.SchemaValidator, error) { + panic("unimplemented") +} + +// Unzip extracts a ZIP file to the specified destination +func unzip(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + // Ensure the destination directory exists + if err := os.MkdirAll(dest, 0755); err != nil { + return err + } + + for _, f := range r.File { + + fpath := filepath.Join(dest, f.Name) + // Ensure directory exists + log.Debugf(context.Background(), "Pain : fpath: %s,filepath.Dir(fpath): %s", fpath, filepath.Dir(fpath)) + if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return err + } + // Open the file inside the zip + srcFile, err := f.Open() + if err != nil { + return err + } + defer srcFile.Close() + + // Create the destination file + dstFile, err := os.Create(fpath) + if err != nil { + return err + } + defer dstFile.Close() + + // Copy file contents + if _, err := io.Copy(dstFile, srcFile); err != nil { + return err + } + } + + return nil } diff --git a/pkg/response/response.go b/pkg/response/response.go index 310d06f..9cb7e2c 100644 --- a/pkg/response/response.go +++ b/pkg/response/response.go @@ -3,141 +3,121 @@ package response import ( "context" "encoding/json" + "errors" "fmt" + "net/http" + "strconv" + + "github.com/beckn/beckn-onix/pkg/model" ) +// ErrorType represents different types of errors in the Beckn protocol. type ErrorType string const ( + // SchemaValidationErrorType represents an error due to schema validation failure. SchemaValidationErrorType ErrorType = "SCHEMA_VALIDATION_ERROR" - InvalidRequestErrorType ErrorType = "INVALID_REQUEST" + + // InvalidRequestErrorType represents an error due to an invalid request. + InvalidRequestErrorType ErrorType = "INVALID_REQUEST" ) +// BecknRequest represents a generic Beckn request with an optional context. type BecknRequest struct { Context map[string]interface{} `json:"context,omitempty"` } -type Error struct { - Code string `json:"code,omitempty"` - Message string `json:"message,omitempty"` - Paths string `json:"paths,omitempty"` -} - -type Message struct { - Ack struct { - Status string `json:"status,omitempty"` - } `json:"ack,omitempty"` - Error *Error `json:"error,omitempty"` -} - -type BecknResponse struct { - Context map[string]interface{} `json:"context,omitempty"` - Message Message `json:"message,omitempty"` -} - -type ClientFailureBecknResponse struct { - Context map[string]interface{} `json:"context,omitempty"` - Error *Error `json:"error,omitempty"` -} - -var errorMap = map[ErrorType]Error{ - SchemaValidationErrorType: { - Code: "400", - Message: "Schema validation failed", - }, - InvalidRequestErrorType: { - Code: "401", - Message: "Invalid request format", - }, -} - -var DefaultError = Error{ - Code: "500", - Message: "Internal server error", -} - -func Nack(ctx context.Context, tp ErrorType, paths string, body []byte) ([]byte, error) { - var req BecknRequest - if err := json.Unmarshal(body, &req); err != nil { - return nil, fmt.Errorf("failed to parse request: %w", err) - } - - errorObj, ok := errorMap[tp] - if paths != "" { - errorObj.Paths = paths - } - - var response BecknResponse - - if !ok { - response = BecknResponse{ - Context: req.Context, - Message: Message{ - Ack: struct { - Status string `json:"status,omitempty"` - }{ - Status: "NACK", - }, - Error: &DefaultError, - }, - } - } else { - response = BecknResponse{ - Context: req.Context, - Message: Message{ - Ack: struct { - Status string `json:"status,omitempty"` - }{ - Status: "NACK", - }, - Error: &errorObj, - }, - } - } - - return json.Marshal(response) -} - -func Ack(ctx context.Context, body []byte) ([]byte, error) { - var req BecknRequest - if err := json.Unmarshal(body, &req); err != nil { - return nil, fmt.Errorf("failed to parse request: %w", err) - } - - response := BecknResponse{ - Context: req.Context, - Message: Message{ - Ack: struct { - Status string `json:"status,omitempty"` - }{ - Status: "ACK", +// SendAck sends an acknowledgment (ACK) response indicating a successful request processing. +func SendAck(w http.ResponseWriter) { + // Create the response object + resp := &model.Response{ + Message: model.Message{ + Ack: model.Ack{ + Status: model.StatusACK, }, }, } - return json.Marshal(response) -} - -func HandleClientFailure(ctx context.Context, tp ErrorType, body []byte) ([]byte, error) { - var req BecknRequest - if err := json.Unmarshal(body, &req); err != nil { - return nil, fmt.Errorf("failed to parse request: %w", err) + // Marshal to JSON + data, err := json.Marshal(resp) + if err != nil { + http.Error(w, "failed to marshal response", http.StatusInternalServerError) + return } - errorObj, ok := errorMap[tp] - var response ClientFailureBecknResponse + // Set headers and write response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(data) +} - if !ok { - response = ClientFailureBecknResponse{ - Context: req.Context, - Error: &DefaultError, - } - } else { - response = ClientFailureBecknResponse{ - Context: req.Context, - Error: &errorObj, - } +// nack sends a negative acknowledgment (NACK) response with an error message. +func nack(w http.ResponseWriter, err *model.Error, status int) { + // Create the NACK response object + resp := &model.Response{ + Message: model.Message{ + Ack: model.Ack{ + Status: model.StatusNACK, + }, + Error: err, + }, } - return json.Marshal(response) + // Marshal the response to JSON + data, jsonErr := json.Marshal(resp) + if jsonErr != nil { + http.Error(w, "failed to marshal response", http.StatusInternalServerError) + return + } + + // Set headers and write response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) // Assuming NACK means a bad request + w.Write(data) +} + +func internalServerError(ctx context.Context) *model.Error { + return &model.Error{ + Message: fmt.Sprintf("Internal server error, MessageID: %s", ctx.Value(model.MsgIDKey)), + } +} + +// SendNack sends a negative acknowledgment (NACK) response with an error message. +func SendNack(ctx context.Context, w http.ResponseWriter, err error) { + var schemaErr *model.SchemaValidationErr + var signErr *model.SignValidationErr + var badReqErr *model.BadReqErr + var notFoundErr *model.NotFoundErr + + switch { + case errors.As(err, &schemaErr): // Custom application error + nack(w, schemaErr.BecknError(), http.StatusBadRequest) + return + case errors.As(err, &signErr): + nack(w, signErr.BecknError(), http.StatusUnauthorized) + return + case errors.As(err, &badReqErr): + nack(w, badReqErr.BecknError(), http.StatusBadRequest) + return + case errors.As(err, ¬FoundErr): + nack(w, notFoundErr.BecknError(), http.StatusNotFound) + return + default: + nack(w, internalServerError(ctx), http.StatusInternalServerError) + return + } +} + +// BecknError generates a standardized Beckn error response. +func BecknError(ctx context.Context, err error, status int) *model.Error { + msg := err.Error() + msgID := ctx.Value(model.MsgIDKey) + if status == http.StatusInternalServerError { + + msg = "Internal server error" + } + return &model.Error{ + Message: fmt.Sprintf("%s. MessageID: %s.", msg, msgID), + Code: strconv.Itoa(status), + } } diff --git a/pkg/response/response_test.go b/pkg/response/response_test.go deleted file mode 100644 index 242fa72..0000000 --- a/pkg/response/response_test.go +++ /dev/null @@ -1,303 +0,0 @@ -package response - -import ( - "context" - "encoding/json" - "reflect" - "testing" -) - -func TestNack(t *testing.T) { - ctx := context.Background() - - tests := []struct { - name string - errorType ErrorType - requestBody string - wantStatus string - wantErrCode string - wantErrMsg string - wantErr bool - path string - }{ - { - name: "Schema validation error", - errorType: SchemaValidationErrorType, - requestBody: `{"context": {"domain": "test-domain", "location": "test-location"}}`, - wantStatus: "NACK", - wantErrCode: "400", - wantErrMsg: "Schema validation failed", - wantErr: false, - path: "test", - }, - { - name: "Invalid request error", - errorType: InvalidRequestErrorType, - requestBody: `{"context": {"domain": "test-domain"}}`, - wantStatus: "NACK", - wantErrCode: "401", - wantErrMsg: "Invalid request format", - wantErr: false, - path: "test", - }, - { - name: "Unknown error type", - errorType: "UNKNOWN_ERROR", - requestBody: `{"context": {"domain": "test-domain"}}`, - wantStatus: "NACK", - wantErrCode: "500", - wantErrMsg: "Internal server error", - wantErr: false, - path: "test", - }, - { - name: "Empty request body", - errorType: SchemaValidationErrorType, - requestBody: `{}`, - wantStatus: "NACK", - wantErrCode: "400", - wantErrMsg: "Schema validation failed", - wantErr: false, - path: "test", - }, - { - name: "Invalid JSON", - errorType: SchemaValidationErrorType, - requestBody: `{invalid json}`, - wantErr: true, - path: "test", - }, - { - name: "Complex nested context", - errorType: SchemaValidationErrorType, - requestBody: `{"context": {"domain": "test-domain", "nested": {"key1": "value1", "key2": 123}}}`, - wantStatus: "NACK", - wantErrCode: "400", - wantErrMsg: "Schema validation failed", - wantErr: false, - path: "test", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - resp, err := Nack(ctx, tt.errorType, tt.path, []byte(tt.requestBody)) - - if (err != nil) != tt.wantErr { - t.Errorf("Nack() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr && err != nil { - return - } - - var becknResp BecknResponse - if err := json.Unmarshal(resp, &becknResp); err != nil { - t.Errorf("Failed to unmarshal response: %v", err) - return - } - - if becknResp.Message.Ack.Status != tt.wantStatus { - t.Errorf("Nack() status = %v, want %v", becknResp.Message.Ack.Status, tt.wantStatus) - } - - if becknResp.Message.Error.Code != tt.wantErrCode { - t.Errorf("Nack() error code = %v, want %v", becknResp.Message.Error.Code, tt.wantErrCode) - } - - if becknResp.Message.Error.Message != tt.wantErrMsg { - t.Errorf("Nack() error message = %v, want %v", becknResp.Message.Error.Message, tt.wantErrMsg) - } - - var origReq BecknRequest - if err := json.Unmarshal([]byte(tt.requestBody), &origReq); err == nil { - if !compareContexts(becknResp.Context, origReq.Context) { - t.Errorf("Nack() context not preserved, got = %v, want %v", becknResp.Context, origReq.Context) - } - } - }) - } -} - -func TestAck(t *testing.T) { - ctx := context.Background() - - tests := []struct { - name string - requestBody string - wantStatus string - wantErr bool - }{ - { - name: "Valid request", - requestBody: `{"context": {"domain": "test-domain", "location": "test-location"}}`, - wantStatus: "ACK", - wantErr: false, - }, - { - name: "Empty context", - requestBody: `{"context": {}}`, - wantStatus: "ACK", - wantErr: false, - }, - { - name: "Invalid JSON", - requestBody: `{invalid json}`, - wantErr: true, - }, - { - name: "Complex nested context", - requestBody: `{"context": {"domain": "test-domain", "nested": {"key1": "value1", "key2": 123, "array": [1,2,3]}}}`, - wantStatus: "ACK", - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - resp, err := Ack(ctx, []byte(tt.requestBody)) - - if (err != nil) != tt.wantErr { - t.Errorf("Ack() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr && err != nil { - return - } - - var becknResp BecknResponse - if err := json.Unmarshal(resp, &becknResp); err != nil { - t.Errorf("Failed to unmarshal response: %v", err) - return - } - - if becknResp.Message.Ack.Status != tt.wantStatus { - t.Errorf("Ack() status = %v, want %v", becknResp.Message.Ack.Status, tt.wantStatus) - } - - if becknResp.Message.Error != nil { - t.Errorf("Ack() should not have error, got %v", becknResp.Message.Error) - } - - var origReq BecknRequest - if err := json.Unmarshal([]byte(tt.requestBody), &origReq); err == nil { - if !compareContexts(becknResp.Context, origReq.Context) { - t.Errorf("Ack() context not preserved, got = %v, want %v", becknResp.Context, origReq.Context) - } - } - }) - } -} - -func TestHandleClientFailure(t *testing.T) { - ctx := context.Background() - - tests := []struct { - name string - errorType ErrorType - requestBody string - wantErrCode string - wantErrMsg string - wantErr bool - }{ - { - name: "Schema validation error", - errorType: SchemaValidationErrorType, - requestBody: `{"context": {"domain": "test-domain", "location": "test-location"}}`, - wantErrCode: "400", - wantErrMsg: "Schema validation failed", - wantErr: false, - }, - { - name: "Invalid request error", - errorType: InvalidRequestErrorType, - requestBody: `{"context": {"domain": "test-domain"}}`, - wantErrCode: "401", - wantErrMsg: "Invalid request format", - wantErr: false, - }, - { - name: "Unknown error type", - errorType: "UNKNOWN_ERROR", - requestBody: `{"context": {"domain": "test-domain"}}`, - wantErrCode: "500", - wantErrMsg: "Internal server error", - wantErr: false, - }, - { - name: "Invalid JSON", - errorType: SchemaValidationErrorType, - requestBody: `{invalid json}`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - resp, err := HandleClientFailure(ctx, tt.errorType, []byte(tt.requestBody)) - - if (err != nil) != tt.wantErr { - t.Errorf("HandleClientFailure() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr && err != nil { - return - } - - var failureResp ClientFailureBecknResponse - if err := json.Unmarshal(resp, &failureResp); err != nil { - t.Errorf("Failed to unmarshal response: %v", err) - return - } - - if failureResp.Error.Code != tt.wantErrCode { - t.Errorf("HandleClientFailure() error code = %v, want %v", failureResp.Error.Code, tt.wantErrCode) - } - - if failureResp.Error.Message != tt.wantErrMsg { - t.Errorf("HandleClientFailure() error message = %v, want %v", failureResp.Error.Message, tt.wantErrMsg) - } - - var origReq BecknRequest - if err := json.Unmarshal([]byte(tt.requestBody), &origReq); err == nil { - if !compareContexts(failureResp.Context, origReq.Context) { - t.Errorf("HandleClientFailure() context not preserved, got = %v, want %v", failureResp.Context, origReq.Context) - } - } - }) - } -} - -func TestErrorMap(t *testing.T) { - - expectedTypes := []ErrorType{ - SchemaValidationErrorType, - InvalidRequestErrorType, - } - - for _, tp := range expectedTypes { - if _, exists := errorMap[tp]; !exists { - t.Errorf("ErrorType %v not found in errorMap", tp) - } - } - - if DefaultError.Code != "500" || DefaultError.Message != "Internal server error" { - t.Errorf("DefaultError not set correctly, got code=%v, message=%v", DefaultError.Code, DefaultError.Message) - } -} - -func compareContexts(c1, c2 map[string]interface{}) bool { - - if c1 == nil && c2 == nil { - return true - } - - if c1 == nil && len(c2) == 0 || c2 == nil && len(c1) == 0 { - return true - } - - return reflect.DeepEqual(c1, c2) -} From ff680dacbb90e3bcab2152f5b1d11ad0b8296b72 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Wed, 26 Mar 2025 11:54:27 +0530 Subject: [PATCH 02/15] Added Test cases for the module - Code coverage for Module is 93.1% --- core/module/handler/config.go | 6 +- core/module/handler/stdHandler.go | 2 +- core/module/module_test.go | 165 ++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 core/module/module_test.go diff --git a/core/module/handler/config.go b/core/module/handler/config.go index a33aa21..6803add 100644 --- a/core/module/handler/config.go +++ b/core/module/handler/config.go @@ -44,8 +44,8 @@ const ( HandlerTypeLookup Type = "lookUp" ) -// pluginCfg holds the configuration for various plugins. -type pluginCfg struct { +// PluginCfg holds the configuration for various plugins. +type PluginCfg struct { SchemaValidator *plugin.Config `yaml:"schemaValidator,omitempty"` SignValidator *plugin.Config `yaml:"signValidator,omitempty"` Publisher *plugin.Config `yaml:"publisher,omitempty"` @@ -59,7 +59,7 @@ type pluginCfg struct { // Config holds the configuration for request processing handlers. type Config struct { - Plugins pluginCfg `yaml:"plugins"` + Plugins PluginCfg `yaml:"plugins"` Steps []string Type Type RegistryURL string `yaml:"registryUrl"` diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index bc6786b..e0f999b 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -189,7 +189,7 @@ func loadKeyManager(ctx context.Context, mgr PluginManager, cache definition.Cac } // initPlugins initializes required plugins for the processor. -func (h *stdHandler) initPlugins(ctx context.Context, mgr PluginManager, cfg *pluginCfg, regURL string) error { +func (h *stdHandler) initPlugins(ctx context.Context, mgr PluginManager, cfg *PluginCfg, regURL string) error { var err error if h.cache, err = loadPlugin(ctx, "Cache", cfg.Cache, mgr.Cache); err != nil { return err diff --git a/core/module/module_test.go b/core/module/module_test.go new file mode 100644 index 0000000..56901f8 --- /dev/null +++ b/core/module/module_test.go @@ -0,0 +1,165 @@ +package module + +import ( + "context" + "errors" + "net/http" + "testing" + + "github.com/beckn/beckn-onix/core/module/handler" + "github.com/beckn/beckn-onix/pkg/plugin" + "github.com/beckn/beckn-onix/pkg/plugin/definition" +) + +// mockPluginManager is a mock implementation of the PluginManager interface +// with support for dynamically setting behavior. +type mockPluginManager struct { + middlewareFunc func(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) +} + +// Middleware returns a mock middleware function based on the provided configuration. +func (m *mockPluginManager) Middleware(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) { + return m.middlewareFunc(ctx, cfg) +} + +// SignValidator returns a mock verifier implementation. +func (m *mockPluginManager) SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error) { + return nil, nil +} + +// Validator returns a mock schema validator implementation. +func (m *mockPluginManager) Validator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) { + return nil, nil +} + +// Router returns a mock router implementation. +func (m *mockPluginManager) Router(ctx context.Context, cfg *plugin.Config) (definition.Router, error) { + return nil, nil +} + +// Publisher returns a mock publisher implementation. +func (m *mockPluginManager) Publisher(ctx context.Context, cfg *plugin.Config) (definition.Publisher, error) { + return nil, nil +} + +// Signer returns a mock signer implementation. +func (m *mockPluginManager) Signer(ctx context.Context, cfg *plugin.Config) (definition.Signer, error) { + return nil, nil +} + +// Step returns a mock step implementation. +func (m *mockPluginManager) Step(ctx context.Context, cfg *plugin.Config) (definition.Step, error) { + return nil, nil +} + +// Cache returns a mock cache implementation. +func (m *mockPluginManager) Cache(ctx context.Context, cfg *plugin.Config) (definition.Cache, error) { + return nil, nil +} + +// KeyManager returns a mock key manager implementation. +func (m *mockPluginManager) KeyManager(ctx context.Context, cache definition.Cache, rLookup definition.RegistryLookup, cfg *plugin.Config) (definition.KeyManager, error) { + return nil, nil +} + +// SchemaValidator returns a mock schema validator implementation. +func (m *mockPluginManager) SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) { + return nil, nil +} + +// TestRegisterSuccess tests scenarios where the handler registration should succeed. +func TestRegisterSuccess(t *testing.T) { + tests := []struct { + name string + mCfgs []Config + mockManager *mockPluginManager + }{ + { + name: "successful registration", + mCfgs: []Config{ + { + Name: "test-module", + Path: "/test", + Handler: handler.Config{ + Type: handler.HandlerTypeStd, + Plugins: handler.PluginCfg{ + Middleware: []plugin.Config{{ID: "mock-middleware"}}, + }, + }, + }, + }, + mockManager: &mockPluginManager{ + middlewareFunc: func(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + }) + }, nil + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mux := http.NewServeMux() + err := Register(context.Background(), tt.mCfgs, mux, tt.mockManager) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} + +// TestRegisterFailure tests scenarios where the handler registration should fail. +func TestRegisterFailure(t *testing.T) { + tests := []struct { + name string + mCfgs []Config + mockManager *mockPluginManager + }{ + { + name: "invalid handler type", + mCfgs: []Config{ + { + Name: "invalid-module", + Path: "/invalid", + Handler: handler.Config{ + Type: "invalid-type", + }, + }, + }, + mockManager: &mockPluginManager{}, + }, + { + name: "middleware error", + mCfgs: []Config{ + { + Name: "test-module", + Path: "/test", + Handler: handler.Config{ + Type: handler.HandlerTypeStd, + Plugins: handler.PluginCfg{ + Middleware: []plugin.Config{{ID: "mock-middleware"}}, + }, + }, + }, + }, + mockManager: &mockPluginManager{ + middlewareFunc: func(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) { + return nil, errors.New("middleware error") + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mux := http.NewServeMux() + err := Register(context.Background(), tt.mCfgs, mux, tt.mockManager) + if err == nil { + t.Errorf("expected an error but got nil") + } + }) + } +} From 75883c6f6a81c39d47422d048a8e6a853ca13799 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Wed, 26 Mar 2025 14:33:32 +0530 Subject: [PATCH 03/15] Resolved linting issues --- cmd/adapter/main.go | 4 +++- core/module/handler/config.go | 5 ++++- core/module/handler/step.go | 15 --------------- pkg/response/response.go | 11 +++++++++-- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index d65e616..063377e 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -109,7 +109,9 @@ func run(ctx context.Context, configPath string) error { return fmt.Errorf("failed to initialize config: %w", err) } log.Infof(ctx, "Initializing logger with config: %+v", cfg.Log) - log.InitLogger(cfg.Log) + if err := log.InitLogger(cfg.Log); err != nil { + return fmt.Errorf("failed to initialize logger: %w", err) + } // Initialize plugin manager. log.Infof(ctx, "Initializing plugin manager") diff --git a/core/module/handler/config.go b/core/module/handler/config.go index 6803add..08d90a3 100644 --- a/core/module/handler/config.go +++ b/core/module/handler/config.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/beckn/beckn-onix/pkg/log" "github.com/beckn/beckn-onix/pkg/model" "github.com/beckn/beckn-onix/pkg/plugin" "github.com/beckn/beckn-onix/pkg/plugin/definition" @@ -112,6 +113,8 @@ func (s *Step) UnmarshalYAML(unmarshal func(interface{}) error) error { func DummyHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte("Dummy Handler Response")) + if _, err := w.Write([]byte("Dummy Handler Response")); err != nil { + log.Error(context.Background(), err, "failed to write nack response") + } }), nil } diff --git a/core/module/handler/step.go b/core/module/handler/step.go index 448a937..bcd185d 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -164,18 +164,3 @@ func (b *broadcastStep) Run(ctx *model.StepContext) error { // TODO: Implement broadcast logic if needed return nil } - -// subscribeStep is a stub for subscription handling. -type subscribeStep struct{} - -// Run is a placeholder for future implementation. -func (s *subscribeStep) Run(ctx *model.StepContext) error { - // TODO: Implement subscription logic if needed - return nil -} - -// tracingStep wraps a Step with OpenTelemetry tracing -type tracingStep struct { - step definition.Step - name string -} diff --git a/pkg/response/response.go b/pkg/response/response.go index 9cb7e2c..d5326ac 100644 --- a/pkg/response/response.go +++ b/pkg/response/response.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" + "github.com/beckn/beckn-onix/pkg/log" "github.com/beckn/beckn-onix/pkg/model" ) @@ -48,7 +49,10 @@ func SendAck(w http.ResponseWriter) { // Set headers and write response w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write(data) + if _, err := w.Write(data); err != nil { + log.Error(context.Background(), err, "failed to write ack response") + } + } // nack sends a negative acknowledgment (NACK) response with an error message. @@ -73,7 +77,10 @@ func nack(w http.ResponseWriter, err *model.Error, status int) { // Set headers and write response w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) // Assuming NACK means a bad request - w.Write(data) + if _, err := w.Write(data); err != nil { + log.Error(context.Background(), err, "failed to write nack response") + } + } func internalServerError(ctx context.Context) *model.Error { From 2fa34759bc5e2993300c1c807fa7abbf224d86ba Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Wed, 26 Mar 2025 18:15:06 +0530 Subject: [PATCH 04/15] Added test case for Adapter 1. Removed config files from config folder. 2. Added test cases for the Adapter with code coverage of 91%. 3. Resolved linting issues. --- cmd/adapter/main.go | 25 +- cmd/adapter/main_test.go | 514 ++++ config/bap.yaml | 69 - config/bapCallerRouting-config.yaml | 3 - config/bpp-local.yaml | 63 - config/bpp.yaml | 63 - config/bppCallerRouting-config.yaml | 3 - config/bppRecieverRouting-config.yaml | 3 - config/byuerApp-config.yaml | 2 - config/onix/bapTxnCaller-routing.yaml | 4 - config/onix/bapTxnReciever-routing.yaml | 4 - config/onix/bppTxnCaller-routing.yaml | 4 - config/onix/bppTxnReciever-routing.yaml | 4 - config/onix/onix-adapter.yaml | 231 -- config/onix/plugin.yaml | 8 - config/onix/schemas/core/v1.1.0/Cancel.json | 32 - config/onix/schemas/core/v1.1.0/Confirm.json | 43 - config/onix/schemas/core/v1.1.0/Init.json | 43 - config/onix/schemas/core/v1.1.0/OnCancel.json | 43 - .../onix/schemas/core/v1.1.0/OnConfirm.json | 43 - config/onix/schemas/core/v1.1.0/OnInit.json | 43 - config/onix/schemas/core/v1.1.0/OnRating.json | 46 - config/onix/schemas/core/v1.1.0/OnSearch.json | 43 - config/onix/schemas/core/v1.1.0/OnSelect.json | 40 - config/onix/schemas/core/v1.1.0/OnStatus.json | 43 - .../onix/schemas/core/v1.1.0/OnSupport.json | 40 - config/onix/schemas/core/v1.1.0/OnTrack.json | 43 - config/onix/schemas/core/v1.1.0/OnUpdate.json | 43 - config/onix/schemas/core/v1.1.0/Rating.json | 42 - config/onix/schemas/core/v1.1.0/Response.json | 7 - config/onix/schemas/core/v1.1.0/Status.json | 43 - config/onix/schemas/core/v1.1.0/Support.json | 39 - config/onix/schemas/core/v1.1.0/Track.json | 47 - config/onix/schemas/core/v1.1.0/Update.json | 53 - .../onix/schemas/core/v1.1.0/definitions.json | 2459 ----------------- .../onix/schemas/core/v1.1.0/on_cancel.json | 46 - .../onix/schemas/core/v1.1.0/on_confirm.json | 46 - config/onix/schemas/core/v1.1.0/on_init.json | 46 - .../onix/schemas/core/v1.1.0/on_rating.json | 47 - .../onix/schemas/core/v1.1.0/on_search.json | 46 - .../onix/schemas/core/v1.1.0/on_select.json | 43 - .../onix/schemas/core/v1.1.0/on_status.json | 46 - .../onix/schemas/core/v1.1.0/on_support.json | 42 - config/onix/schemas/core/v1.1.0/on_track.json | 46 - .../onix/schemas/core/v1.1.0/on_update.json | 46 - config/onix/schemas/core/v1.1.0/search.json | 26 - config/onix/schemas/core/v1.1.0/select.json | 43 - .../schemas/ondc_trv10/v2.0.0/cancel.json | 40 - .../schemas/ondc_trv10/v2.0.0/confirm.json | 463 ---- .../onix/schemas/ondc_trv10/v2.0.0/init.json | 550 ---- .../schemas/ondc_trv10/v2.0.0/on_cancel.json | 13 - .../schemas/ondc_trv10/v2.0.0/on_confirm.json | 314 --- .../schemas/ondc_trv10/v2.0.0/on_init.json | 317 --- .../schemas/ondc_trv10/v2.0.0/on_rating.json | 13 - .../schemas/ondc_trv10/v2.0.0/on_search.json | 644 ----- .../schemas/ondc_trv10/v2.0.0/on_select.json | 230 -- .../schemas/ondc_trv10/v2.0.0/on_status.json | 13 - .../schemas/ondc_trv10/v2.0.0/on_support.json | 13 - .../schemas/ondc_trv10/v2.0.0/on_track.json | 13 - .../schemas/ondc_trv10/v2.0.0/on_update.json | 13 - .../schemas/ondc_trv10/v2.0.0/rating.json | 13 - .../schemas/ondc_trv10/v2.0.0/search.json | 146 - .../schemas/ondc_trv10/v2.0.0/select.json | 16 - .../schemas/ondc_trv10/v2.0.0/status.json | 26 - .../schemas/ondc_trv10/v2.0.0/support.json | 13 - .../onix/schemas/ondc_trv10/v2.0.0/track.json | 13 - .../schemas/ondc_trv10/v2.0.0/update.json | 35 - config/sellerData.yaml | 69 - go.mod | 25 +- go.sum | 27 +- 70 files changed, 549 insertions(+), 7228 deletions(-) create mode 100644 cmd/adapter/main_test.go delete mode 100644 config/bap.yaml delete mode 100644 config/bapCallerRouting-config.yaml delete mode 100644 config/bpp-local.yaml delete mode 100644 config/bpp.yaml delete mode 100644 config/bppCallerRouting-config.yaml delete mode 100644 config/bppRecieverRouting-config.yaml delete mode 100644 config/byuerApp-config.yaml delete mode 100644 config/onix/bapTxnCaller-routing.yaml delete mode 100644 config/onix/bapTxnReciever-routing.yaml delete mode 100644 config/onix/bppTxnCaller-routing.yaml delete mode 100644 config/onix/bppTxnReciever-routing.yaml delete mode 100644 config/onix/onix-adapter.yaml delete mode 100644 config/onix/plugin.yaml delete mode 100644 config/onix/schemas/core/v1.1.0/Cancel.json delete mode 100644 config/onix/schemas/core/v1.1.0/Confirm.json delete mode 100644 config/onix/schemas/core/v1.1.0/Init.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnCancel.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnConfirm.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnInit.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnRating.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnSearch.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnSelect.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnStatus.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnSupport.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnTrack.json delete mode 100644 config/onix/schemas/core/v1.1.0/OnUpdate.json delete mode 100644 config/onix/schemas/core/v1.1.0/Rating.json delete mode 100644 config/onix/schemas/core/v1.1.0/Response.json delete mode 100644 config/onix/schemas/core/v1.1.0/Status.json delete mode 100644 config/onix/schemas/core/v1.1.0/Support.json delete mode 100644 config/onix/schemas/core/v1.1.0/Track.json delete mode 100644 config/onix/schemas/core/v1.1.0/Update.json delete mode 100644 config/onix/schemas/core/v1.1.0/definitions.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_cancel.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_confirm.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_init.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_rating.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_search.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_select.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_status.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_support.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_track.json delete mode 100644 config/onix/schemas/core/v1.1.0/on_update.json delete mode 100644 config/onix/schemas/core/v1.1.0/search.json delete mode 100644 config/onix/schemas/core/v1.1.0/select.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/cancel.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/confirm.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/init.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_init.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_search.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_select.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_status.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_support.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_track.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/on_update.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/rating.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/search.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/select.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/status.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/support.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/track.json delete mode 100644 config/onix/schemas/ondc_trv10/v2.0.0/update.json delete mode 100644 config/sellerData.yaml diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index 063377e..621b24b 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -19,8 +19,8 @@ import ( "github.com/beckn/beckn-onix/pkg/plugin" ) -// config struct holds all configurations. -type config struct { +// Config struct holds all configurations. +type Config struct { AppName string `yaml:"appName"` Log log.Config `yaml:"log"` PluginManager *plugin.ManagerConfig `yaml:"pluginManager"` @@ -40,6 +40,7 @@ type timeoutConfig struct { } var configPath string +var runFunc = run func main() { // Define and parse command-line flags. @@ -50,14 +51,14 @@ func main() { log.Infof(context.Background(), "Starting application with config: %s", configPath) // Run the application within a context. - if err := run(context.Background(), configPath); err != nil { + if err := runFunc(context.Background(), configPath); err != nil { log.Fatalf(context.Background(), err, "Application failed: %v", err) } log.Infof(context.Background(), "Application finished") } // initConfig loads and validates the configuration. -func initConfig(ctx context.Context, path string) (*config, error) { +func initConfig(ctx context.Context, path string) (*Config, error) { // Open the configuration file. file, err := os.Open(path) if err != nil { @@ -66,7 +67,7 @@ func initConfig(ctx context.Context, path string) (*config, error) { defer file.Close() // Decode the YAML configuration. - var cfg config + var cfg Config if err := yaml.NewDecoder(file).Decode(&cfg); err != nil { return nil, fmt.Errorf("could not decode config: %w", err) } @@ -80,7 +81,7 @@ func initConfig(ctx context.Context, path string) (*config, error) { } // validateConfig validates the configuration. -func validateConfig(cfg *config) error { +func validateConfig(cfg *Config) error { if strings.TrimSpace(cfg.AppName) == "" { return fmt.Errorf("missing app name") } @@ -91,7 +92,7 @@ func validateConfig(cfg *config) error { } // newServer creates and initializes the HTTP server. -func newServer(ctx context.Context, mgr handler.PluginManager, cfg *config) (http.Handler, error) { +func newServer(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) { mux := http.NewServeMux() err := module.Register(ctx, cfg.Modules, mux, mgr) if err != nil { @@ -100,6 +101,10 @@ func newServer(ctx context.Context, mgr handler.PluginManager, cfg *config) (htt return mux, nil } +var newManagerFunc = plugin.NewManager +var newServerFunc = newServer +var initLoggerFunc = log.InitLogger + // run encapsulates the application logic. func run(ctx context.Context, configPath string) error { closers := []func(){} @@ -109,13 +114,13 @@ func run(ctx context.Context, configPath string) error { return fmt.Errorf("failed to initialize config: %w", err) } log.Infof(ctx, "Initializing logger with config: %+v", cfg.Log) - if err := log.InitLogger(cfg.Log); err != nil { + if err := initLoggerFunc(cfg.Log); err != nil { return fmt.Errorf("failed to initialize logger: %w", err) } // Initialize plugin manager. log.Infof(ctx, "Initializing plugin manager") - mgr, closer, err := plugin.NewManager(ctx, cfg.PluginManager) + mgr, closer, err := newManagerFunc(ctx, cfg.PluginManager) if err != nil { return fmt.Errorf("failed to create plugin manager: %w", err) } @@ -124,7 +129,7 @@ func run(ctx context.Context, configPath string) error { // Initialize HTTP server. log.Infof(ctx, "Initializing HTTP server") - srv, err := newServer(ctx, mgr, cfg) + srv, err := newServerFunc(ctx, mgr, cfg) if err != nil { return fmt.Errorf("failed to initialize server: %w", err) } diff --git a/cmd/adapter/main_test.go b/cmd/adapter/main_test.go new file mode 100644 index 0000000..3576ca3 --- /dev/null +++ b/cmd/adapter/main_test.go @@ -0,0 +1,514 @@ +package main + +import ( + "context" + "errors" + "flag" + "net/http" + "os" + "strings" + "testing" + "time" + + "github.com/beckn/beckn-onix/core/module" + "github.com/beckn/beckn-onix/core/module/handler" + "github.com/beckn/beckn-onix/pkg/plugin" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/stretchr/testify/mock" +) + +// MockPluginManager implements handler.PluginManager for testing. +type MockPluginManager struct { + mock.Mock +} + +// Middleware returns a middleware function based on the provided configuration. +func (m *MockPluginManager) Middleware(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) { + args := m.Called(ctx, cfg) + return args.Get(0).(func(http.Handler) http.Handler), args.Error(1) +} + +// SignValidator returns a mock implementation of the Verifier interface. +func (m *MockPluginManager) SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error) { + return nil, nil +} + +// Validator returns a mock implementation of the SchemaValidator interface. +func (m *MockPluginManager) Validator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) { + return nil, nil +} + +// Router returns a mock implementation of the Router interface. +func (m *MockPluginManager) Router(ctx context.Context, cfg *plugin.Config) (definition.Router, error) { + return nil, nil +} + +// Publisher returns a mock implementation of the Publisher interface. +func (m *MockPluginManager) Publisher(ctx context.Context, cfg *plugin.Config) (definition.Publisher, error) { + return nil, nil +} + +// Signer returns a mock implementation of the Signer interface. +func (m *MockPluginManager) Signer(ctx context.Context, cfg *plugin.Config) (definition.Signer, error) { + return nil, nil +} + +// Step returns a mock implementation of the Step interface. +func (m *MockPluginManager) Step(ctx context.Context, cfg *plugin.Config) (definition.Step, error) { + return nil, nil +} + +// Cache returns a mock implementation of the Cache interface. +func (m *MockPluginManager) Cache(ctx context.Context, cfg *plugin.Config) (definition.Cache, error) { + return nil, nil +} + +// KeyManager returns a mock implementation of the KeyManager interface. +func (m *MockPluginManager) KeyManager(ctx context.Context, cache definition.Cache, rLookup definition.RegistryLookup, cfg *plugin.Config) (definition.KeyManager, error) { + return nil, nil +} + +// SchemaValidator returns a mock implementation of the SchemaValidator interface. +func (m *MockPluginManager) SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) { + return nil, nil +} + +// testConfigContent defines a mock configuration used in tests. +const testConfigContent = ` +appName: "TestApp" +http: + port: "8080" + timeout: + read: 5 + write: 5 + idle: 10 +` + +// initLogger is a mock function to initialize the logger. +var initLogger = func(cfg *Config) error { + return nil +} + +// mockRun is a mock implementation of the `run` function, simulating a successful run. +func mockRun(ctx context.Context, configPath string) error { + return nil // Simulate a successful run +} + +// TestMainFunction tests the main function execution, including command-line argument parsing. +func TestMainFunction(t *testing.T) { + // Backup original run function and restore it after test + origRun := runFunc + defer func() { runFunc = origRun }() + runFunc = mockRun + + origArgs := os.Args + defer func() { os.Args = origArgs }() + + // Set mock command-line arguments + os.Args = []string{"cmd", "-config=../../config/test-config.yaml"} + + fs := flag.NewFlagSet("test", flag.ExitOnError) + fs.StringVar(&configPath, "config", "../../config/clientSideHandler-config.yaml", "Path to the configuration file") + + fs.Parse(os.Args[1:]) + main() +} + +// TestRunSuccess tests the successful execution of the run function with different configurations. +func TestRunSuccess(t *testing.T) { + tests := []struct { + name string + configData string + mockMgr func() (*plugin.Manager, func(), error) + mockLogger func(cfg *Config) error + mockServer func(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) + }{ + { + name: "Valid Config", + configData: "valid_config.yaml", + mockMgr: func() (*plugin.Manager, func(), error) { + return &plugin.Manager{}, func() {}, nil + }, + mockLogger: func(cfg *Config) error { + return nil + }, + mockServer: func(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) { + return http.NewServeMux(), nil + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + testFilePath := tt.configData + mockConfig := `appName: "testAdapter" +log: + level: debug + destinations: + - type: stdout + context_keys: + - transaction_id + - message_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +plugin: + root: "/mock/plugins" + pluginZipPath: "/mock/plugins/plugins_bundle.zip" + plugins: + - testPlugin1 + - testPlugin2 +modules: + - name: testModule + type: transaction + path: /testPath + targetType: msgQ + plugin: + schemaValidator: + id: testValidator + publisher: + id: testPublisher + config: + project: test-project + topic: test-topic + router: + id: testRouter + config: + routingConfigPath: "/mock/configs/testRouting-config.yaml"` + + err := os.WriteFile(testFilePath, []byte(mockConfig), 0644) + if err != nil { + t.Errorf("Failed to create test config file: %v", err) + } + defer os.Remove(testFilePath) + + // Mock dependencies + originalNewManager := newManagerFunc + newManagerFunc = func(ctx context.Context, cfg *plugin.ManagerConfig) (*plugin.Manager, func(), error) { + return tt.mockMgr() + } + defer func() { newManagerFunc = originalNewManager }() + + originalNewServer := newServerFunc + newServerFunc = func(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) { + return tt.mockServer(ctx, mgr, cfg) + } + defer func() { newServerFunc = originalNewServer }() + + // Run function + err = run(ctx, testFilePath) + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + }) + } +} + +// TestRunFailure validates failure scenarios for the run function. +func TestRunFailure(t *testing.T) { + tests := []struct { + name string + configData string + mockMgr func() (*plugin.Manager, func(), error) + mockLogger func(cfg *Config) error + mockServer func(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) + expectedErr string + }{ + { + name: "Invalid Config File", + configData: "invalid_config.yaml", + mockMgr: func() (*plugin.Manager, func(), error) { + return &plugin.Manager{}, func() {}, nil + }, + mockLogger: func(cfg *Config) error { + return nil + }, + mockServer: func(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) { + return nil, errors.New("failed to start server") + }, + expectedErr: "failed to initialize config: invalid config: missing app name", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + testFilePath := tt.configData + mockConfig := `invalid: "config"` + err := os.WriteFile(testFilePath, []byte(mockConfig), 0644) + if err != nil { + t.Errorf("Failed to create test config file: %v", err) + } + defer os.Remove(testFilePath) + + // Mock dependencies + originalNewManager := newManagerFunc + newManagerFunc = func(ctx context.Context, cfg *plugin.ManagerConfig) (*plugin.Manager, func(), error) { + return tt.mockMgr() + } + defer func() { newManagerFunc = originalNewManager }() + + originalNewServer := newServerFunc + newServerFunc = func(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) { + return tt.mockServer(ctx, mgr, cfg) + } + defer func() { newServerFunc = originalNewServer }() + + // Run function + err = run(ctx, testFilePath) + if err == nil { + t.Errorf("Expected error, but got nil") + } else if err.Error() != tt.expectedErr { + t.Errorf("Expected error '%s', but got '%s'", tt.expectedErr, err.Error()) + } + }) + } +} + +// TestInitConfigSuccess tests the successful initialization of the config. +func TestInitConfigSuccess(t *testing.T) { + tests := []struct { + name string + configData string + }{ + { + name: "Valid Config", + configData: ` +appName: "TestApp" +http: + port: "8080" + timeout: + read: 5 + write: 5 + idle: 10 +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + configPath := "test_config_success.yaml" + defer os.Remove(configPath) + + err := os.WriteFile(configPath, []byte(tt.configData), 0644) + if err != nil { + t.Errorf("Failed to create test config file: %v", err) + } + + _, err = initConfig(context.Background(), configPath) + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + }) + } +} + +// TestInitConfigFailure tests failure scenarios for config initialization. +func TestInitConfigFailure(t *testing.T) { + tests := []struct { + name string + configData string + expectedErr string + }{ + { + name: "Invalid YAML Format", + configData: `appName: "TestApp"\nhttp: { invalid_yaml }`, + expectedErr: "could not decode config", + }, + { + name: "Missing Required Fields", + configData: `appName: ""\nhttp:\n timeout:\n read: 5\n`, + expectedErr: "could not decode config: yaml: did not find expected key", + }, + { + name: "Non-Existent File", + configData: "", + expectedErr: "could not open config file", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + configPath := "test_config_failure.yaml" + + if tt.configData != "" { + err := os.WriteFile(configPath, []byte(tt.configData), 0644) + if err != nil { + t.Errorf("Failed to create test config file: %v", err) + } + defer os.Remove(configPath) + } else { + // Ensure file does not exist for non-existent file test + os.Remove(configPath) + } + + _, err := initConfig(context.Background(), configPath) + if err == nil { + t.Errorf("Expected error but got nil") + } else if !strings.Contains(err.Error(), tt.expectedErr) { + t.Errorf("Expected error containing '%s', but got '%s'", tt.expectedErr, err.Error()) + } + }) + } +} + +// TestNewServerSuccess tests successful server creation. +func TestNewServerSuccess(t *testing.T) { + tests := []struct { + name string + modules []module.Config + }{ + { + name: "Successful server creation with no modules", + modules: []module.Config{}, // No modules to simplify the test + }, + } + + mockMgr := new(MockPluginManager) // Mocking PluginManager + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{ + Modules: tt.modules, + HTTP: httpConfig{ + Port: "8080", + Timeout: timeoutConfig{ + Read: 5, + Write: 5, + Idle: 10, + }, + }, + } + + handler, err := newServer(context.Background(), mockMgr, cfg) + + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + if handler == nil { + t.Errorf("Expected handler to be non-nil, but got nil") + } + }) + } +} + +// TestNewServerFailure tests failure scenarios when creating a server. +func TestNewServerFailure(t *testing.T) { + tests := []struct { + name string + modules []module.Config + }{ + { + name: "Module registration failure", + modules: []module.Config{ + { + Name: "InvalidModule", + Path: "/invalid", + }, + }, + }, + } + + mockMgr := new(MockPluginManager) // Mocking PluginManager + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{ + Modules: tt.modules, + HTTP: httpConfig{ + Port: "8080", + Timeout: timeoutConfig{ + Read: 5, + Write: 5, + Idle: 10, + }, + }, + } + + handler, err := newServer(context.Background(), mockMgr, cfg) + + if err == nil { + t.Errorf("Expected an error, but got nil") + } + if handler != nil { + t.Errorf("Expected handler to be nil, but got a non-nil value") + } + }) + } +} + +// TestValidateConfigSuccess tests validation of a correct config. +func TestValidateConfigSuccess(t *testing.T) { + tests := []struct { + name string + cfg Config + }{ + { + name: "Valid Config", + cfg: Config{ + AppName: "TestApp", + HTTP: httpConfig{ + Port: "8080", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateConfig(&tt.cfg) + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + }) + } +} + +// TestValidateConfigFailure tests validation failures for incorrect config. +func TestValidateConfigFailure(t *testing.T) { + tests := []struct { + name string + cfg Config + expectedErr string + }{ + { + name: "Missing AppName", + cfg: Config{ + AppName: "", + HTTP: httpConfig{ + Port: "8080", + }, + }, + expectedErr: "missing app name", + }, + { + name: "Missing Port", + cfg: Config{ + AppName: "TestApp", + HTTP: httpConfig{ + Port: "", + }, + }, + expectedErr: "missing port", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateConfig(&tt.cfg) + if err == nil { + t.Errorf("Expected error '%s', but got nil", tt.expectedErr) + } else if err.Error() != tt.expectedErr { + t.Errorf("Expected error '%s', but got '%s'", tt.expectedErr, err.Error()) + } + }) + } +} diff --git a/config/bap.yaml b/config/bap.yaml deleted file mode 100644 index 25c67b2..0000000 --- a/config/bap.yaml +++ /dev/null @@ -1,69 +0,0 @@ -appName: "bapAdapter" -log: - level: debug - destinations: - - type: stdout - context_keys: - - transaction_id - - message_id -http: - port: 8080 - timeout: - read: 30 - write: 30 - idle: 30 -plugin: - root: /app/plugins - pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip - plugins: - - publisher Src version raw comp zip - - nopschemavalidator - - router - - nopsigner - - nopsignvalidator - - reqpreprocessor - - gcpAuthMdw -modules: - - name: reciever - type: transaction - path: /reciever - targetType: msgQ - plugin: - schemaValidator: - id: nopschemavalidator - signValidator: - id: nopsignvalidator - publisher: - id: publisher - config: - project: ondc-seller-dev - topic: bapNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bapRecieverRouting-config.yaml - preProcessors: - - id: reqpreprocessor - steps: - steps: - - addRoute - signValidate - -addRout - customValidate - - name: transactionCaller - path: /caller - targetType: "http" - plugin: - signer: - id: nopsigner - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bapCallerRouting-config.yaml - preProcessors: - - id: reqpreprocessor - # postProcessors: - # - id: gcpAuthMdw - # config: - # audience: https://bpp-adapter-903496459467.asia-southeast1.run.app - # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bapCallerRouting-config.yaml b/config/bapCallerRouting-config.yaml deleted file mode 100644 index a6702ea..0000000 --- a/config/bapCallerRouting-config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -routes: - - action: search - target: https://bpp-adapter-903496459467.asia-southeast1.run.app/reciever \ No newline at end of file diff --git a/config/bpp-local.yaml b/config/bpp-local.yaml deleted file mode 100644 index 5af87f4..0000000 --- a/config/bpp-local.yaml +++ /dev/null @@ -1,63 +0,0 @@ -appName: "bppClientService" -log: - level: debug - destinations: - - type: stdout - context_keys: - - transaction_id - - message_id -http: - port: 8080 - timeout: - read: 30 - write: 30 - idle: 30 -plugin: - root: extracted/plugins - pluginZipPath: plugins_bundle.zip - plugins: - - publisher - - nopschemavalidator - - router - - nopsigner - - nopsignvalidator - - reqpreprocessor - - gcpAuthMdw -module: - modules: - - name: transactionReciever - path: /reciever - targetType: msgQ - plugin: - schemaValidator: - id: nopschemavalidator - signValidator: - id: nopsignValidator - publisher: - id: publisher - config: - project: ondc-seller-dev - topic: clientSideTopic - Router: - id: router - config: - routingConfigPath: configs/bppRecieverRouting-config.yaml - preProcessors: - - id: reqpreprocessor - - name: transactionCaller - path: /caller - targetType: "http" - plugin: - signer: - id: nopsigner - Router: - id: router - config: - routingConfigPath: configs/bppCallerRouting-config.yaml - preProcessors: - - id: reqpreprocessor - postProcessors: - - id: gcpAuthMdw - config: - audience: "target" - diff --git a/config/bpp.yaml b/config/bpp.yaml deleted file mode 100644 index a4d9a65..0000000 --- a/config/bpp.yaml +++ /dev/null @@ -1,63 +0,0 @@ -appName: "bppClientService" -log: - level: debug - destinations: - - type: stdout - context_keys: - - transaction_id - - message_id -http: - port: 8080 - timeout: - read: 30 - write: 30 - idle: 30 -plugin: - root: /app/plugins - pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip - plugins: - - publisher - - nopschemavalidator - - router - - nopsigner - - nopsignvalidator - - reqpreprocessor - - gcpAuthMdw -module: - modules: - - name: transactionReciever - path: /reciever - targetType: msgQ - plugin: - schemaValidator: - id: nopschemavalidator - signValidator: - id: nopsignvalidator - publisher: - id: publisher - config: - project: ondc-seller-dev - topic: bppNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bppRecieverRouting-config.yaml - preProcessors: - - id: reqpreprocessor - - name: transactionCaller - path: /caller - targetType: "http" - plugin: - signer: - id: nopsigner - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bppCallerRouting-config.yaml - preProcessors: - - id: reqpreprocessor - # postProcessors: - # - id: gcpAuthMdw - # config: - # audience: https://bap-adapter-903496459467.asia-southeast1.run.app - # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bppCallerRouting-config.yaml b/config/bppCallerRouting-config.yaml deleted file mode 100644 index 9aa5e39..0000000 --- a/config/bppCallerRouting-config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -routes: - - action: on_search - target: targeturl \ No newline at end of file diff --git a/config/bppRecieverRouting-config.yaml b/config/bppRecieverRouting-config.yaml deleted file mode 100644 index 15fdb33..0000000 --- a/config/bppRecieverRouting-config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -routes: - - action: search - target: https://sellerapp-903496459467.asia-southeast1.run.app \ No newline at end of file diff --git a/config/byuerApp-config.yaml b/config/byuerApp-config.yaml deleted file mode 100644 index a441371..0000000 --- a/config/byuerApp-config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -bap_url: "https://bap-csr-903496459467.asia-southeast1.run.app" # Replace with actual Beckn API endpoint -port: "8080" # The port on which the server will run \ No newline at end of file diff --git a/config/onix/bapTxnCaller-routing.yaml b/config/onix/bapTxnCaller-routing.yaml deleted file mode 100644 index 3d59ad0..0000000 --- a/config/onix/bapTxnCaller-routing.yaml +++ /dev/null @@ -1,4 +0,0 @@ -routes: - - action: search - type: url - target: http://localhost:8080/bpp/reciever/search \ No newline at end of file diff --git a/config/onix/bapTxnReciever-routing.yaml b/config/onix/bapTxnReciever-routing.yaml deleted file mode 100644 index 7c4da0d..0000000 --- a/config/onix/bapTxnReciever-routing.yaml +++ /dev/null @@ -1,4 +0,0 @@ -routes: - - action: on_search - type: publisher - target: bapNetworkReciever \ No newline at end of file diff --git a/config/onix/bppTxnCaller-routing.yaml b/config/onix/bppTxnCaller-routing.yaml deleted file mode 100644 index 688b9af..0000000 --- a/config/onix/bppTxnCaller-routing.yaml +++ /dev/null @@ -1,4 +0,0 @@ -routes: - - action: on_search - type: url - target: http://localhost:8080/bap/reciever/on_search \ No newline at end of file diff --git a/config/onix/bppTxnReciever-routing.yaml b/config/onix/bppTxnReciever-routing.yaml deleted file mode 100644 index e9ba98c..0000000 --- a/config/onix/bppTxnReciever-routing.yaml +++ /dev/null @@ -1,4 +0,0 @@ -routes: - - action: search - type: publisher - target: bapNetworkReciever \ No newline at end of file diff --git a/config/onix/onix-adapter.yaml b/config/onix/onix-adapter.yaml deleted file mode 100644 index 3697136..0000000 --- a/config/onix/onix-adapter.yaml +++ /dev/null @@ -1,231 +0,0 @@ -appName: "onix" -log: - level: debug - destinations: - - type: stdout - contextKeys: - - transaction_id - - message_id - - subscriber_id -http: - port: 8080 - timeout: - read: 30 - write: 30 - idle: 30 -pluginManager: - root: /app/plugins - remoteRoot: /mnt/gcs/plugins/plugins_bundle.zip -modules: - - name: bapTxnReciever - path: /bap/reciever/ - handler: - type: std - role: bap - trace: - # validateSign: true - # addRoute: true - # validateSchema: true - # reqpreprocessor: true - registryUrl: http://localhost:8080/reg - plugins: - keyManager: - id: secretskeymanager - config: - projectID: trusty-relic-370809 - cache: - id: redis - config: - addr: 10.81.192.4:6379 - # schemaValidator: - # id: schemavalidator - # config: - # schemaDir: /mnt/gcs/configs/schemas - signValidator: - id: signvalidator - publisher: - id: publisher - config: - project: trusty-relic-370809 - topic: bapNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bapTxnReciever-routing.yaml - middleware: - - id: reqpreprocessor - config: - uuidKeys: transaction_id,message_id - role: bap - steps: - - validateSign - - addRoute - # - validateSchema - - name: bapTxnCaller - path: /bap/caller/ - handler: - type: std - registryUrl: http://localhost:8080/reg - role: bap - plugins: - keyManager: - id: secretskeymanager - config: - projectID: trusty-relic-370809 - cache: - id: redis - config: - addr: 10.81.192.4:6379 - # schemaValidator: - # id: schemavalidator - # config: - # schemaDir: /mnt/gcs/configs/schemas - signer: - id: signer - publisher: - id: publisher - config: - project: trusty-relic-370809 - topic: bapNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bapTxnCaller-routing.yaml - middleware: - - id: reqpreprocessor - config: - uuidKeys: transaction_id,message_id - role: bap - steps: - # - validateSchema - - addRoute - - sign - - name: bapSubscribeCaller - path: /bap/subscribe - handler: - type: npSub - role: bap - registryUrl: http://localhost:8080/reg - plugins: - keyManager: - id: secretskeymanager - config: - projectID: trusty-relic-370809 - cache: - id: redis - config: - addr: 10.81.192.4:6379 - - name: bppTxnReciever - path: /bpp/reciever/ - handler: - type: std - role: bpp - subscriberId: bpp1 - registryUrl: http://localhost:8080/reg - plugins: - keyManager: - id: secretskeymanager - config: - projectID: trusty-relic-370809 - cache: - id: redis - config: - addr: 10.81.192.4:6379 - # schemaValidator: - # id: schemavalidator - # config: - # schemaDir: /mnt/gcs/configs/schemas - signValidator: - id: signvalidator - publisher: - id: publisher - config: - project: trusty-relic-370809 - topic: bapNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bppTxnReciever-routing.yaml - middleware: - - id: reqpreprocessor - config: - uuidKeys: transaction_id,message_id - role: bpp - steps: - - validateSign - - addRoute - # - validateSchema - - name: bppTxnCaller - path: /bpp/caller/ - handler: - type: std - role: bpp - registryUrl: http://localhost:8080/reg - plugins: - keyManager: - id: secretskeymanager - config: - projectID: trusty-relic-370809 - cache: - id: redis - config: - addr: 10.81.192.4:6379 - # schemaValidator: - # id: schemavalidator - # config: - # schemaDir: /mnt/gcs/configs/schemas - signer: - id: signer - publisher: - id: publisher - config: - project: trusty-relic-370809 - topic: bapNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bppTxnCaller-routing.yaml - middleware: - - id: reqpreprocessor - config: - uuidKeys: transaction_id,message_id - role: bpp - steps: - # - validateSchema - - addRoute - - sign - - name: bppSubscribeCaller - path: /bpp/subscribe - handler: - type: npSub - role: bpp - registryUrl: http://localhost:8080/reg - plugins: - keyManager: - id: secretskeymanager - config: - projectID: trusty-relic-370809 - cache: - id: redis - config: - addr: 10.81.192.4:6379 - - name: regSubscribeReciever - path: /reg/subscribe - handler: - type: regSub - role: registery - plugins: - cache: - id: redis - config: - addr: "10.81.192.4:6379" - - name: regLookUpReciever - path: /reg/lookUp - handler: - type: lookUp - role: registery - plugins: - cache: - id: redis - config: - addr: "10.81.192.4:6379" diff --git a/config/onix/plugin.yaml b/config/onix/plugin.yaml deleted file mode 100644 index 4c2bb72..0000000 --- a/config/onix/plugin.yaml +++ /dev/null @@ -1,8 +0,0 @@ -plugins: - - gcpAuthMdw - - nopsigner - - router - - publisher - - reqpreprocessor - - nopschemavalidator - - nopsignvalidator \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Cancel.json b/config/onix/schemas/core/v1.1.0/Cancel.json deleted file mode 100644 index b09e97a..0000000 --- a/config/onix/schemas/core/v1.1.0/Cancel.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "cancel", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "cancel" - ] - } - }, - "required": [ - "action" - ] - } - ] - } - - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Confirm.json b/config/onix/schemas/core/v1.1.0/Confirm.json deleted file mode 100644 index d4b4277..0000000 --- a/config/onix/schemas/core/v1.1.0/Confirm.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "confirm", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "confirm" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Init.json b/config/onix/schemas/core/v1.1.0/Init.json deleted file mode 100644 index fec48db..0000000 --- a/config/onix/schemas/core/v1.1.0/Init.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "init", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "init" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnCancel.json b/config/onix/schemas/core/v1.1.0/OnCancel.json deleted file mode 100644 index 4eafef2..0000000 --- a/config/onix/schemas/core/v1.1.0/OnCancel.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnCancel", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_cancel" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnConfirm.json b/config/onix/schemas/core/v1.1.0/OnConfirm.json deleted file mode 100644 index 6043056..0000000 --- a/config/onix/schemas/core/v1.1.0/OnConfirm.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnConfirm", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_confirm" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnInit.json b/config/onix/schemas/core/v1.1.0/OnInit.json deleted file mode 100644 index be74e86..0000000 --- a/config/onix/schemas/core/v1.1.0/OnInit.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnInit", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_init" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnRating.json b/config/onix/schemas/core/v1.1.0/OnRating.json deleted file mode 100644 index 8864f06..0000000 --- a/config/onix/schemas/core/v1.1.0/OnRating.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnRating", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_rating" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "feedback_form": { - "description": "A feedback form to allow the user to provide additional information on the rating provided", - "allOf": [ - { - "$ref": "./definitions.json#/$defs/XInput" - } - ] - } - } - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context", - "message" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnSearch.json b/config/onix/schemas/core/v1.1.0/OnSearch.json deleted file mode 100644 index 982b26d..0000000 --- a/config/onix/schemas/core/v1.1.0/OnSearch.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnSearch", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_search" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "catalog": { - "$ref": "./definitions.json#/$defs/Catalog" - } - }, - "required": [ - "catalog" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnSelect.json b/config/onix/schemas/core/v1.1.0/OnSelect.json deleted file mode 100644 index fa86378..0000000 --- a/config/onix/schemas/core/v1.1.0/OnSelect.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnSelect", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_select" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - } - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnStatus.json b/config/onix/schemas/core/v1.1.0/OnStatus.json deleted file mode 100644 index 7453a11..0000000 --- a/config/onix/schemas/core/v1.1.0/OnStatus.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnStatus", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_status" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnSupport.json b/config/onix/schemas/core/v1.1.0/OnSupport.json deleted file mode 100644 index ed91c42..0000000 --- a/config/onix/schemas/core/v1.1.0/OnSupport.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnSupport", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_support" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "support": { - "$ref": "./definitions.json#/$defs/Support" - } - } - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnTrack.json b/config/onix/schemas/core/v1.1.0/OnTrack.json deleted file mode 100644 index 7d46838..0000000 --- a/config/onix/schemas/core/v1.1.0/OnTrack.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnTrack", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_track" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "tracking": { - "$ref": "./definitions.json#/$defs/Tracking" - } - }, - "required": [ - "tracking" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/OnUpdate.json b/config/onix/schemas/core/v1.1.0/OnUpdate.json deleted file mode 100644 index 2b2f2e8..0000000 --- a/config/onix/schemas/core/v1.1.0/OnUpdate.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "OnUpdate", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "properties": { - "action": { - "enum": [ - "on_update" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Rating.json b/config/onix/schemas/core/v1.1.0/Rating.json deleted file mode 100644 index 3006d12..0000000 --- a/config/onix/schemas/core/v1.1.0/Rating.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "rating", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "rating" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "ratings": { - "type": "array", - "items": { - "$ref": "./definitions.json#/$defs/Rating" - } - } - } - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Response.json b/config/onix/schemas/core/v1.1.0/Response.json deleted file mode 100644 index cbd7372..0000000 --- a/config/onix/schemas/core/v1.1.0/Response.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "Response", - "type": "object", - "properties": {}, - "required": [] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Status.json b/config/onix/schemas/core/v1.1.0/Status.json deleted file mode 100644 index 871f693..0000000 --- a/config/onix/schemas/core/v1.1.0/Status.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "status", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "status" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "ref_id": { - "$ref": "./definitions.json#/$defs/Order" - }, - "order_id": { - "$ref": "./definitions.json#/$defs/Order" - } - } - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Support.json b/config/onix/schemas/core/v1.1.0/Support.json deleted file mode 100644 index 729b81a..0000000 --- a/config/onix/schemas/core/v1.1.0/Support.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "support", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "support" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "support": { - "$ref": "./definitions.json#/$defs/Support" - } - } - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Track.json b/config/onix/schemas/core/v1.1.0/Track.json deleted file mode 100644 index ec891b1..0000000 --- a/config/onix/schemas/core/v1.1.0/Track.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "track", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "track" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order_id": { - "$ref": "./definitions.json#/$defs/Order" - }, - "callback_url": { - "type": "string", - "format": "uri" - } - }, - "required": [ - "order_id" - ] - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/Update.json b/config/onix/schemas/core/v1.1.0/Update.json deleted file mode 100644 index 6a664fa..0000000 --- a/config/onix/schemas/core/v1.1.0/Update.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "update", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "update" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "update_target": { - "description": "Comma separated values of order objects being updated. For example: ```\"update_target\":\"item,billing,fulfillment\"```", - "type": "string" - }, - "order": { - "description": "Updated order object", - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Order" - } - ] - } - }, - "required": [ - "update_target", - "order" - ] - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/definitions.json b/config/onix/schemas/core/v1.1.0/definitions.json deleted file mode 100644 index ed7eec1..0000000 --- a/config/onix/schemas/core/v1.1.0/definitions.json +++ /dev/null @@ -1,2459 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "definitions.json", - "$defs": { - "Ack": { - "$id": "Ack", - "description": "Describes the acknowledgement sent in response to an API call. If the implementation uses HTTP/S, then Ack must be returned in the same session. Every API call to a BPP must be responded to with an Ack whether the BPP intends to respond with a callback or not. This has one property called `status` that indicates the status of the Acknowledgement.", - "type": "object", - "additionalProperties": false, - "properties": { - "status": { - "type": "string", - "description": "The status of the acknowledgement. If the request passes the validation criteria of the BPP, then this is set to ACK. If a BPP responds with status = `ACK` to a request, it is required to respond with a callback. If the request fails the validation criteria, then this is set to NACK. Additionally, if a BPP does not intend to respond with a callback even after the request meets the validation criteria, it should set this value to `NACK`.", - "enum": [ - "ACK", - "NACK" - ] - }, - "tags": { - "description": "A list of tags containing any additional information sent along with the Acknowledgement.", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "AddOn": { - "$id": "AddOn", - "description": "Describes an additional item offered as a value-addition to a product or service. This does not exist independently in a catalog and is always associated with an item.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "Provider-defined ID of the add-on", - "type": "string" - }, - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "price": { - "$ref": "definitions.json#/$defs/Price" - }, - "quantity": { - "$ref": "definitions.json#/$defs/ItemQuantity" - } - } - }, - "Address": { - "$id": "Address", - "description": "Describes a postal address.", - "type": "string" - }, - "Agent": { - "$id": "Agent", - "description": "Describes the direct performer, driver or executor that fulfills an order. It is usually a person. But in some rare cases, it could be a non-living entity like a drone, or a bot. Some examples of agents are Doctor in the healthcare sector, a driver in the mobility sector, or a delivery person in the logistics sector. This object can be set at any stage of the order lifecycle. This can be set at the discovery stage when the BPP wants to provide details on the agent fulfilling the order, like in healthcare, where the doctor's name appears during search. This object can also used to search for a particular person that the customer wants fulfilling an order. Sometimes, this object gets instantiated after the order is confirmed, like in the case of on-demand taxis, where the driver is assigned after the user confirms the ride.", - "type": "object", - "additionalProperties": false, - "properties": { - "person": { - "$ref": "definitions.json#/$defs/Person" - }, - "contact": { - "$ref": "definitions.json#/$defs/Contact" - }, - "organization": { - "$ref": "definitions.json#/$defs/Organization" - }, - "rating": { - "$ref": "definitions.json#/$defs/Rating/properties/value" - } - } - }, - "Authorization": { - "$id": "Authorization", - "description": "Describes an authorization mechanism used to start or end the fulfillment of an order. For example, in the mobility sector, the driver may require a one-time password to initiate the ride. In the healthcare sector, a patient may need to provide a password to open a video conference link during a teleconsultation.", - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "description": "Type of authorization mechanism used. The allowed values for this field can be published as part of the network policy.", - "type": "string" - }, - "token": { - "description": "Token used for authorization. This is typically generated at the BPP. The BAP can send this value to the user via any channel that it uses to authenticate the user like SMS, Email, Push notification, or in-app rendering.", - "type": "string" - }, - "valid_from": { - "description": "Timestamp in RFC3339 format from which token is valid", - "type": "string", - "format": "date-time" - }, - "valid_to": { - "description": "Timestamp in RFC3339 format until which token is valid", - "type": "string", - "format": "date-time" - }, - "status": { - "description": "Status of the token", - "type": "string" - } - } - }, - "Billing": { - "$id": "Billing", - "description": "Describes the billing details of an entity.
This has properties like name,organization,address,email,phone,time,tax_number, created_at,updated_at", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "description": "Name of the billable entity", - "type": "string" - }, - "organization": { - "description": "Details of the organization being billed.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Organization" - } - ] - }, - "address": { - "description": "The address of the billable entity", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Address" - } - ] - }, - "state": { - "description": "The state where the billable entity resides. This is important for state-level tax calculation", - "allOf": [ - { - "$ref": "definitions.json#/$defs/State" - } - ] - }, - "city": { - "description": "The city where the billable entity resides.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/City" - } - ] - }, - "email": { - "description": "Email address where the bill is sent to", - "type": "string", - "format": "email" - }, - "phone": { - "description": "Phone number of the billable entity", - "type": "string" - }, - "time": { - "description": "Details regarding the billing period", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Time" - } - ] - }, - "tax_id": { - "description": "ID of the billable entity as recognized by the taxation authority", - "type": "string" - } - } - }, - "Cancellation": { - "$id": "Cancellation", - "description": "Describes a cancellation event", - "type": "object", - "additionalProperties": false, - "properties": { - "time": { - "description": "Date-time when the order was cancelled by the buyer", - "type": "string", - "format": "date-time" - }, - "cancelled_by": { - "type": "string", - "enum": [ - "CONSUMER", - "PROVIDER" - ] - }, - "reason": { - "description": "The reason for cancellation", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Option" - } - ] - }, - "additional_description": { - "description": "Any additional information regarding the nature of cancellation", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Descriptor" - } - ] - } - } - }, - "CancellationTerm": { - "$id": "CancellationTerm", - "description": "Describes the cancellation terms of an item or an order. This can be referenced at an item or order level. Item-level cancellation terms can override the terms at the order level.", - "type": "object", - "additionalProperties": false, - "properties": { - "fulfillment_state": { - "description": "The state of fulfillment during which this term is applicable.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/FulfillmentState" - } - ] - }, - "reason_required": { - "description": "Indicates whether a reason is required to cancel the order", - "type": "boolean" - }, - "cancel_by": { - "description": "Information related to the time of cancellation.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Time" - } - ] - }, - "cancellation_fee": { - "$ref": "definitions.json#/$defs/Fee" - }, - "xinput": { - "$ref": "definitions.json#/$defs/XInput" - }, - "external_ref": { - "$ref": "definitions.json#/$defs/MediaFile" - } - } - }, - "Catalog": { - "$id": "Catalog", - "description": "Describes the products or services offered by a BPP. This is typically sent as the response to a search intent from a BAP. The payment terms, offers and terms of fulfillment supported by the BPP can also be included here. The BPP can show hierarchical nature of products/services in its catalog using the parent_category_id in categories. The BPP can also send a ttl (time to live) in the context which is the duration for which a BAP can cache the catalog and use the cached catalog.
This has properties like bbp/descriptor,bbp/categories,bbp/fulfillments,bbp/payments,bbp/offers,bbp/providers and exp
This is used in the following situations.
  • This is typically used in the discovery stage when the BPP sends the details of the products and services it offers as response to a search intent from the BAP.
", - "type": "object", - "additionalProperties": false, - "properties": { - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "fulfillments": { - "description": "Fulfillment modes offered at the BPP level. This is used when a BPP itself offers fulfillments on behalf of the providers it has onboarded.", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Fulfillment" - } - }, - "payments": { - "description": "Payment terms offered by the BPP for all transactions. This can be overriden at the provider level.", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Payment" - } - }, - "offers": { - "description": "Offers at the BPP-level. This is common across all providers onboarded by the BPP.", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Offer" - } - }, - "providers": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Provider" - } - }, - "exp": { - "description": "Timestamp after which catalog will expire", - "type": "string", - "format": "date-time" - }, - "ttl": { - "description": "Duration in seconds after which this catalog will expire", - "type": "string" - } - } - }, - "Category": { - "$id": "Category", - "description": "A label under which a collection of items can be grouped.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "ID of the category", - "type": "string" - }, - "parent_category_id": { - "$ref": "definitions.json#/$defs/Category/properties/id" - }, - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "time": { - "$ref": "definitions.json#/$defs/Time" - }, - "ttl": { - "description": "Time to live for an instance of this schema" - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "Circle": { - "$id": "Circle", - "description": "Describes a circular region of a specified radius centered at a specified GPS coordinate.", - "type": "object", - "additionalProperties": false, - "properties": { - "gps": { - "$ref": "definitions.json#/$defs/Gps" - }, - "radius": { - "$ref": "definitions.json#/$defs/Scalar" - } - } - }, - "City": { - "$id": "City", - "description": "Describes a city", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "description": "Name of the city", - "type": "string" - }, - "code": { - "description": "City code", - "type": "string" - } - } - }, - "Contact": { - "$id": "Contact", - "description": "Describes the contact information of an entity", - "type": "object", - "additionalProperties": false, - "properties": { - "phone": { - "type": "string" - }, - "email": { - "type": "string" - }, - "jcard": { - "type": "object", - "additionalProperties": false, - "description": "A Jcard object as per draft-ietf-jcardcal-jcard-03 specification" - } - } - }, - "Context": { - "$id": "Context", - "description": "Every API call in beckn protocol has a context. It provides a high-level overview to the receiver about the nature of the intended transaction. Typically, it is the BAP that sets the transaction context based on the consumer's location and action on their UI. But sometimes, during unsolicited callbacks, the BPP also sets the transaction context but it is usually the same as the context of a previous full-cycle, request-callback interaction between the BAP and the BPP. The context object contains four types of fields.
  1. Demographic information about the transaction using fields like `domain`, `country`, and `region`.
  2. Addressing details like the sending and receiving platform's ID and API URL.
  3. Interoperability information like the protocol version that implemented by the sender and,
  4. Transaction details like the method being called at the receiver's endpoint, the transaction_id that represents an end-to-end user session at the BAP, a message ID to pair requests with callbacks, a timestamp to capture sending times, a ttl to specifiy the validity of the request, and a key to encrypt information if necessary.
This object must be passed in every interaction between a BAP and a BPP. In HTTP/S implementations, it is not necessary to send the context during the synchronous response. However, in asynchronous protocols, the context must be sent during all interactions,", - "type": "object", - "additionalProperties": false, - "properties": { - "domain": { - "description": "Domain code that is relevant to this transaction context", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Domain/properties/code", - "type": "string" - } - ] - }, - "location": { - "description": "The location where the transaction is intended to be fulfilled.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Location" - } - ] - }, - "action": { - "description": "The Beckn protocol method being called by the sender and executed at the receiver.", - "type": "string" - }, - "version": { - "type": "string", - "description": "Version of transaction protocol being used by the sender." - }, - "bap_id": { - "description": "Subscriber ID of the BAP", - "allOf": [ - { - "description": "A globally unique identifier of the platform, Typically it is the fully qualified domain name (FQDN) of the platform.", - "type": "string" - } - ] - }, - "bap_uri": { - "description": "Subscriber URL of the BAP for accepting callbacks from BPPs.", - "allOf": [ - { - "description": "The callback URL of the Subscriber. This should necessarily contain the same domain name as set in `subscriber_id``.", - "type": "string", - "format": "uri" - } - ] - }, - "bpp_id": { - "description": "Subscriber ID of the BPP", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Context/properties/bap_id/allOf/0" - } - ] - }, - "bpp_uri": { - "description": "Subscriber URL of the BPP for accepting calls from BAPs.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Context/properties/bap_uri/allOf/0" - } - ] - }, - "transaction_id": { - "description": "This is a unique value which persists across all API calls from `search` through `confirm`. This is done to indicate an active user session across multiple requests. The BPPs can use this value to push personalized recommendations, and dynamic offerings related to an ongoing transaction despite being unaware of the user active on the BAP.", - "type": "string", - "format": "uuid" - }, - "message_id": { - "description": "This is a unique value which persists during a request / callback cycle. Since beckn protocol APIs are asynchronous, BAPs need a common value to match an incoming callback from a BPP to an earlier call. This value can also be used to ignore duplicate messages coming from the BPP. It is recommended to generate a fresh message_id for every new interaction. When sending unsolicited callbacks, BPPs must generate a new message_id.", - "type": "string", - "format": "uuid" - }, - "timestamp": { - "description": "Time of request generation in RFC3339 format", - "type": "string", - "format": "date-time" - }, - "key": { - "description": "The encryption public key of the sender", - "type": "string" - }, - "ttl": { - "description": "The duration in ISO8601 format after timestamp for which this message holds valid", - "type": "string" - } - } - }, - "Country": { - "$id": "Country", - "description": "Describes a country", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "Name of the country" - }, - "code": { - "type": "string", - "description": "Country code as per ISO 3166-1 and ISO 3166-2 format" - } - } - }, - "Credential": { - "$id": "Credential", - "description": "Describes a credential of an entity - Person or Organization", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "default": "VerifiableCredential" - }, - "url": { - "description": "URL of the credential", - "type": "string", - "format": "uri" - } - } - }, - "Customer": { - "$id": "Customer", - "description": "Describes a customer buying/availing a product or a service", - "type": "object", - "additionalProperties": false, - "properties": { - "person": { - "$ref": "definitions.json#/$defs/Person" - }, - "contact": { - "$ref": "definitions.json#/$defs/Contact" - } - } - }, - "DecimalValue": { - "$id": "DecimalValue", - "description": "Describes a numerical value in decimal form", - "type": "string", - "pattern": "[+-]?([0-9]*[.])?[0-9]+" - }, - "Descriptor": { - "$id": "Descriptor", - "description": "Physical description of something.", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "code": { - "type": "string" - }, - "short_desc": { - "type": "string" - }, - "long_desc": { - "type": "string" - }, - "additional_desc": { - "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "type": "string" - }, - "content_type": { - "type": "string", - "enum": [ - "text/plain", - "text/html", - "application/json" - ] - } - } - }, - "media": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/MediaFile" - } - }, - "images": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Image" - } - } - } - }, - "Domain": { - "$id": "Domain", - "description": "Described the industry sector or sub-sector. The network policy should contain codes for all the industry sectors supported by the network. Domains can be created in varying levels of granularity. The granularity of a domain can be decided by the participants of the network. Too broad domains will result in irrelevant search broadcast calls to BPPs that don't have services supporting the domain. Too narrow domains will result in a large number of registry entries for each BPP. It is recommended that network facilitators actively collaborate with various working groups and network participants to carefully choose domain codes keeping in mind relevance, performance, and opportunity cost. It is recommended that networks choose broad domains like mobility, logistics, healthcare etc, and progressively granularize them as and when the number of network participants for each domain grows large.", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "description": "Name of the domain", - "type": "string" - }, - "code": { - "description": "Standard code representing the domain. The standard is usually published as part of the network policy. Furthermore, the network facilitator should also provide a mechanism to provide the supported domains of a network." - }, - "additional_info": { - "description": "A url that contains addtional information about that domain.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/MediaFile" - } - ] - } - } - }, - "Duration": { - "$id": "Duration", - "description": "Describes duration as per ISO8601 format", - "type": "string" - }, - "Error": { - "$id": "Error", - "description": "Describes an error object that is returned by a BAP, BPP or BG as a response or callback to an action by another network participant. This object is sent when any request received by a network participant is unacceptable. This object can be sent either during Ack or with the callback.", - "type": "object", - "additionalProperties": false, - "properties": { - "code": { - "type": "string", - "description": "Standard error code. For full list of error codes, refer to docs/protocol-drafts/BECKN-005-ERROR-CODES-DRAFT-01.md of this repo\"" - }, - "paths": { - "type": "string", - "description": "Path to json schema generating the error. Used only during json schema validation errors" - }, - "message": { - "type": "string", - "description": "Human readable message describing the error. Used mainly for logging. Not recommended to be shown to the user." - } - } - }, - "Fee": { - "$id": "Fee", - "description": "A fee applied on a particular entity", - "type": "object", - "additionalProperties": false, - "properties": { - "percentage": { - "description": "Percentage of a value", - "allOf": [ - { - "$ref": "definitions.json#/$defs/DecimalValue" - } - ] - }, - "amount": { - "description": "A fixed value", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Price" - } - ] - } - } - }, - "Form": { - "$id": "Form", - "description": "Describes a form", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "The form identifier.", - "type": "string" - }, - "url": { - "description": "The URL from where the form can be fetched. The content fetched from the url must be processed as per the mime_type specified in this object. Once fetched, the rendering platform can choosed to render the form as-is as an embeddable element; or process it further to blend with the theme of the application. In case the interface is non-visual, the the render can process the form data and reproduce it as per the standard specified in the form.", - "type": "string", - "format": "uri" - }, - "data": { - "description": "The form submission data", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "mime_type": { - "description": "This field indicates the nature and format of the form received by querying the url. MIME types are defined and standardized in IETF's RFC 6838.", - "type": "string", - "enum": [ - "text/html", - "application/html", - "application/xml" - ] - }, - "resubmit": { - "type": "boolean" - }, - "multiple_sumbissions": { - "type": "boolean" - } - } - }, - "Fulfillment": { - "$id": "Fulfillment", - "description": "Describes how a an order will be rendered/fulfilled to the end-customer", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "Unique reference ID to the fulfillment of an order", - "type": "string" - }, - "type": { - "description": "A code that describes the mode of fulfillment. This is typically set when there are multiple ways an order can be fulfilled. For example, a retail order can be fulfilled either via store pickup or a home delivery. Similarly, a medical consultation can be provided either in-person or via tele-consultation. The network policy must publish standard fulfillment type codes for the different modes of fulfillment.", - "type": "string" - }, - "rateable": { - "description": "Whether the fulfillment can be rated or not", - "type": "boolean" - }, - "rating": { - "description": "The rating value of the fulfullment service.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Rating/properties/value" - } - ] - }, - "state": { - "description": "The current state of fulfillment. The BPP must set this value whenever the state of the order fulfillment changes and fire an unsolicited `on_status` call.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/FulfillmentState" - } - ] - }, - "tracking": { - "type": "boolean", - "description": "Indicates whether the fulfillment allows tracking", - "default": false - }, - "customer": { - "description": "The person that will ultimately receive the order", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Customer" - } - ] - }, - "agent": { - "description": "The agent that is currently handling the fulfillment of the order", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Agent" - } - ] - }, - "contact": { - "$ref": "definitions.json#/$defs/Contact" - }, - "vehicle": { - "$ref": "definitions.json#/$defs/Vehicle" - }, - "stops": { - "description": "The list of logical stops encountered during the fulfillment of an order.", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Stop" - } - }, - "path": { - "description": "The physical path taken by the agent that can be rendered on a map. The allowed format of this property can be set by the network.", - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "FulfillmentState": { - "$id": "FulfillmentState", - "description": "Describes the state of fulfillment", - "type": "object", - "additionalProperties": false, - "properties": { - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "updated_by": { - "type": "string", - "description": "ID of entity which changed the state" - } - } - }, - "Gps": { - "$id": "Gps", - "description": "Describes a GPS coordinate", - "type": "string", - "pattern": "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?),\\s*[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" - }, - "Image": { - "$id": "Image", - "description": "Describes an image", - "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "description": "URL to the image. This can be a data url or an remote url", - "type": "string", - "format": "uri" - }, - "size_type": { - "description": "The size of the image. The network policy can define the default dimensions of each type", - "type": "string", - "enum": [ - "xs", - "sm", - "md", - "lg", - "xl", - "custom" - ] - }, - "width": { - "description": "Width of the image in pixels", - "type": "string" - }, - "height": { - "description": "Height of the image in pixels", - "type": "string" - } - } - }, - "Intent": { - "$id": "Intent", - "description": "The intent to buy or avail a product or a service. The BAP can declare the intent of the consumer containing
  • What they want (A product, service, offer)
  • Who they want (A seller, service provider, agent etc)
  • Where they want it and where they want it from
  • When they want it (start and end time of fulfillment
  • How they want to pay for it

This has properties like descriptor,provider,fulfillment,payment,category,offer,item,tags
This is typically used by the BAP to send the purpose of the user's search to the BPP. This will be used by the BPP to find products or services it offers that may match the user's intent.
For example, in Mobility, the mobility consumer declares a mobility intent. In this case, the mobility consumer declares information that describes various aspects of their journey like,
  • Where would they like to begin their journey (intent.fulfillment.start.location)
  • Where would they like to end their journey (intent.fulfillment.end.location)
  • When would they like to begin their journey (intent.fulfillment.start.time)
  • When would they like to end their journey (intent.fulfillment.end.time)
  • Who is the transport service provider they would like to avail services from (intent.provider)
  • Who is traveling (This is not recommended in public networks) (intent.fulfillment.customer)
  • What kind of fare product would they like to purchase (intent.item)
  • What add-on services would they like to avail
  • What offers would they like to apply on their booking (intent.offer)
  • What category of services would they like to avail (intent.category)
  • What additional luggage are they carrying
  • How would they like to pay for their journey (intent.payment)

For example, in health domain, a consumer declares the intent for a lab booking the describes various aspects of their booking like,
  • Where would they like to get their scan/test done (intent.fulfillment.start.location)
  • When would they like to get their scan/test done (intent.fulfillment.start.time)
  • When would they like to get the results of their test/scan (intent.fulfillment.end.time)
  • Who is the service provider they would like to avail services from (intent.provider)
  • Who is getting the test/scan (intent.fulfillment.customer)
  • What kind of test/scan would they like to purchase (intent.item)
  • What category of services would they like to avail (intent.category)
  • How would they like to pay for their journey (intent.payment)
", - "type": "object", - "additionalProperties": false, - "properties": { - "descriptor": { - "description": "A raw description of the search intent. Free text search strings, raw audio, etc can be sent in this object.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Descriptor" - } - ] - }, - "provider": { - "description": "The provider from which the customer wants to place to the order from", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Provider" - } - ] - }, - "fulfillment": { - "description": "Details on how the customer wants their order fulfilled", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Fulfillment" - } - ] - }, - "payment": { - "description": "Details on how the customer wants to pay for the order", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Payment" - } - ] - }, - "category": { - "description": "Details on the item category", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Category" - } - ] - }, - "offer": { - "description": "details on the offer the customer wants to avail", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Offer" - } - ] - }, - "item": { - "description": "Details of the item that the consumer wants to order", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Item" - } - ] - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "ItemQuantity": { - "$id": "ItemQuantity", - "description": "Describes the count or amount of an item", - "type": "object", - "additionalProperties": false, - "properties": { - "allocated": { - "description": "This represents the exact quantity allocated for purchase of the item.", - "type": "object", - "additionalProperties": false, - "properties": { - "count": { - "type": "integer", - "minimum": 0 - }, - "measure": { - "$ref": "definitions.json#/$defs/Scalar" - } - } - }, - "available": { - "description": "This represents the exact quantity available for purchase of the item. The buyer can only purchase multiples of this", - "type": "object", - "additionalProperties": false, - "properties": { - "count": { - "type": "integer", - "minimum": 0 - }, - "measure": { - "$ref": "definitions.json#/$defs/Scalar" - } - } - }, - "maximum": { - "description": "This represents the maximum quantity allowed for purchase of the item", - "type": "object", - "additionalProperties": false, - "properties": { - "count": { - "type": "integer", - "minimum": 1 - }, - "measure": { - "$ref": "definitions.json#/$defs/Scalar" - } - } - }, - "minimum": { - "description": "This represents the minimum quantity allowed for purchase of the item", - "type": "object", - "additionalProperties": false, - "properties": { - "count": { - "type": "integer", - "minimum": 0 - }, - "measure": { - "$ref": "definitions.json#/$defs/Scalar" - } - } - }, - "selected": { - "description": "This represents the quantity selected for purchase of the item", - "type": "object", - "additionalProperties": false, - "properties": { - "count": { - "type": "integer", - "minimum": 0 - }, - "measure": { - "$ref": "definitions.json#/$defs/Scalar" - } - } - }, - "unitized": { - "description": "This represents the quantity available in a single unit of the item", - "type": "object", - "additionalProperties": false, - "properties": { - "count": { - "type": "integer", - "minimum": 1, - "maximum": 1 - }, - "measure": { - "$ref": "definitions.json#/$defs/Scalar" - } - } - } - } - }, - "Item": { - "$id": "Item", - "description": "Describes a product or a service offered to the end consumer by the provider. In the mobility sector, it can represent a fare product like one way journey. In the logistics sector, it can represent the delivery service offering. In the retail domain it can represent a product like a grocery item.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "ID of the item.", - "type": "string" - }, - "parent_item_id": { - "description": "ID of the item, this item is a variant of", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Item/properties/id" - } - ] - }, - "parent_item_quantity": { - "description": "The number of units of the parent item this item is a multiple of", - "allOf": [ - { - "$ref": "definitions.json#/$defs/ItemQuantity" - } - ] - }, - "descriptor": { - "description": "Physical description of the item", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Descriptor" - } - ] - }, - "creator": { - "description": "The creator of this item", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Organization" - } - ] - }, - "price": { - "description": "The price of this item, if it has intrinsic value", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Price" - } - ] - }, - "quantity": { - "description": "The selling quantity of the item", - "allOf": [ - { - "$ref": "definitions.json#/$defs/ItemQuantity" - } - ] - }, - "category_ids": { - "description": "Categories this item can be listed under", - "type": "array", - "items": { - "allOf": [ - { - "$ref": "definitions.json#/$defs/Category/properties/id" - } - ] - } - }, - "fulfillment_ids": { - "description": "Modes through which this item can be fulfilled", - "type": "array", - "items": { - "allOf": [ - { - "$ref": "definitions.json#/$defs/Fulfillment/properties/id" - } - ] - } - }, - "location_ids": { - "description": "Provider Locations this item is available in", - "type": "array", - "items": { - "allOf": [ - { - "$ref": "definitions.json#/$defs/Location/properties/id" - } - ] - } - }, - "payment_ids": { - "description": "Payment modalities through which this item can be ordered", - "type": "array", - "items": { - "allOf": [ - { - "$ref": "definitions.json#/$defs/Payment/properties/id" - } - ] - } - }, - "add_ons": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/AddOn" - } - }, - "cancellation_terms": { - "description": "Cancellation terms of this item", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/CancellationTerm" - } - }, - "refund_terms": { - "description": "Refund terms of this item", - "type": "array", - "items": { - "description": "Refund term of an item or an order", - "type": "object", - "additionalProperties": false, - "properties": { - "fulfillment_state": { - "description": "The state of fulfillment during which this term is applicable.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/State" - } - ] - }, - "refund_eligible": { - "description": "Indicates if cancellation will result in a refund", - "type": "boolean" - }, - "refund_within": { - "description": "Time within which refund will be processed after successful cancellation.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Time" - } - ] - }, - "refund_amount": { - "$ref": "definitions.json#/$defs/Price" - } - } - } - }, - "replacement_terms": { - "description": "Terms that are applicable be met when this item is replaced", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/ReplacementTerm" - } - }, - "return_terms": { - "description": "Terms that are applicable when this item is returned", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/ReturnTerm" - } - }, - "xinput": { - "description": "Additional input required from the customer to purchase / avail this item", - "allOf": [ - { - "$ref": "definitions.json#/$defs/XInput" - } - ] - }, - "time": { - "description": "Temporal attributes of this item. This property is used when the item exists on the catalog only for a limited period of time.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Time" - } - ] - }, - "rateable": { - "description": "Whether this item can be rated", - "type": "boolean" - }, - "rating": { - "description": "The rating of the item", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Rating/properties/value" - } - ] - }, - "matched": { - "description": "Whether this item is an exact match of the request", - "type": "boolean" - }, - "related": { - "description": "Whether this item is a related item to the exactly matched item", - "type": "boolean" - }, - "recommended": { - "description": "Whether this item is a recommended item to a response", - "type": "boolean" - }, - "ttl": { - "description": "Time to live in seconds for an instance of this schema", - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "Location": { - "$id": "Location", - "description": "The physical location of something", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "map_url": { - "description": "The url to the map of the location. This can be a globally recognized map url or the one specified by the network policy.", - "type": "string", - "format": "uri" - }, - "gps": { - "description": "The GPS co-ordinates of this location.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Gps" - } - ] - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "address": { - "description": "The address of this location.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Address" - } - ] - }, - "city": { - "description": "The city this location is, or is located within", - "allOf": [ - { - "$ref": "definitions.json#/$defs/City" - } - ] - }, - "district": { - "description": "The state this location is, or is located within", - "type": "string" - }, - "state": { - "description": "The state this location is, or is located within", - "allOf": [ - { - "$ref": "definitions.json#/$defs/State" - } - ] - }, - "country": { - "description": "The country this location is, or is located within", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Country" - } - ] - }, - "area_code": { - "type": "string" - }, - "circle": { - "$ref": "definitions.json#/$defs/Circle" - }, - "polygon": { - "description": "The boundary polygon of this location", - "type": "string" - }, - "3dspace": { - "description": "The three dimensional region describing this location", - "type": "string" - }, - "rating": { - "description": "The rating of this location", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Rating/properties/value" - } - ] - } - } - }, - "MediaFile": { - "$id": "MediaFile", - "description": "This object contains a url to a media file.", - "type": "object", - "additionalProperties": false, - "properties": { - "mimetype": { - "description": "indicates the nature and format of the document, file, or assortment of bytes. MIME types are defined and standardized in IETF's RFC 6838", - "type": "string" - }, - "url": { - "description": "The URL of the file", - "type": "string", - "format": "uri" - }, - "signature": { - "description": "The digital signature of the file signed by the sender", - "type": "string" - }, - "dsa": { - "description": "The signing algorithm used by the sender", - "type": "string" - } - } - }, - "Offer": { - "$id": "Offer", - "description": "An offer associated with a catalog. This is typically used to promote a particular product and enable more purchases.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "location_ids": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Location/properties/id" - } - }, - "category_ids": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Category/properties/id" - } - }, - "item_ids": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Item/properties/id" - } - }, - "time": { - "$ref": "definitions.json#/$defs/Time" - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "Option": { - "$id": "Option", - "description": "Describes a selectable option", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - } - } - }, - "Order": { - "$id": "Order", - "description": "Describes a legal purchase order. It contains the complete details of the legal contract created between the buyer and the seller.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "description": "Human-readable ID of the order. This is generated at the BPP layer. The BPP can either generate order id within its system or forward the order ID created at the provider level." - }, - "ref_order_ids": { - "description": "A list of order IDs to link this order to previous orders.", - "type": "array", - "items": { - "type": "string", - "description": "ID of a previous order" - } - }, - "status": { - "description": "Status of the order. Allowed values can be defined by the network policy", - "type": "string", - "enum": [ - "ACTIVE", - "COMPLETE", - "CANCELLED", - "COMPLETED", - "SOFT_CANCEL" - ] - }, - "type": { - "description": "This is used to indicate the type of order being created to BPPs. Sometimes orders can be linked to previous orders, like a replacement order in a retail domain. A follow-up consultation in healthcare domain. A single order part of a subscription order. The list of order types can be standardized at the network level.", - "type": "string", - "default": "DEFAULT", - "enum": [ - "DRAFT", - "DEFAULT" - ] - }, - "provider": { - "description": "Details of the provider whose catalog items have been selected.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Provider" - } - ] - }, - "items": { - "description": "The items purchased / availed in this order", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Item" - } - }, - "add_ons": { - "description": "The add-ons purchased / availed in this order", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/AddOn" - } - }, - "offers": { - "description": "The offers applied in this order", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Offer" - } - }, - "billing": { - "description": "The billing details of this order", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Billing" - } - ] - }, - "fulfillments": { - "description": "The fulfillments involved in completing this order", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Fulfillment" - } - }, - "cancellation": { - "description": "The cancellation details of this order", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Cancellation" - } - ] - }, - "cancellation_terms": { - "description": "Cancellation terms of this item", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/CancellationTerm" - } - }, - "documents": { - "type": "array", - "items": { - "description": "Documnents associated to the order", - "type": "object", - "additionalProperties": false, - "properties": { - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "mime_type": { - "description": "This field indicates the nature and format of the form received by querying the url. MIME types are defined and standardized in IETF's RFC 6838.", - "type": "string", - "enum": [ - "text/html", - "application/html", - "application/xml", - "application/pdf" - ] - }, - "url": { - "description": "The URL from where the form can be fetched. The content fetched from the url must be processed as per the mime_type specified in this object.", - "type": "string", - "format": "uri" - } - } - } - }, - "refund_terms": { - "description": "Refund terms of this item", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Item/properties/refund_terms/items" - } - }, - "replacement_terms": { - "description": "Replacement terms of this item", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/ReplacementTerm" - } - }, - "return_terms": { - "description": "Return terms of this item", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/ReturnTerm" - } - }, - "quote": { - "description": "The mutually agreed upon quotation for this order.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Quotation" - } - ] - }, - "payments": { - "description": "The terms of settlement for this order", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Payment" - } - }, - "created_at": { - "description": "The date-time of creation of this order", - "type": "string", - "format": "date-time" - }, - "updated_at": { - "description": "The date-time of updated of this order", - "type": "string", - "format": "date-time" - }, - "xinput": { - "description": "Additional input required from the customer to confirm this order", - "allOf": [ - { - "$ref": "definitions.json#/$defs/XInput" - } - ] - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "Organization": { - "$id": "Organization", - "description": "An organization. Usually a recognized business entity.", - "type": "object", - "additionalProperties": false, - "properties": { - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "address": { - "description": "The postal address of the organization", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Address" - } - ] - }, - "state": { - "description": "The state where the organization's address is registered", - "allOf": [ - { - "$ref": "definitions.json#/$defs/State" - } - ] - }, - "city": { - "description": "The city where the the organization's address is registered", - "allOf": [ - { - "$ref": "definitions.json#/$defs/City" - } - ] - }, - "contact": { - "$ref": "definitions.json#/$defs/Contact" - } - } - }, - "Payment": { - "$id": "Payment", - "description": "Describes the terms of settlement between the BAP and the BPP for a single transaction. When instantiated, this object contains
  1. the amount that has to be settled,
  2. The payment destination destination details
  3. When the settlement should happen, and
  4. A transaction reference ID
. During a transaction, the BPP reserves the right to decide the terms of payment. However, the BAP can send its terms to the BPP first. If the BPP does not agree to those terms, it must overwrite the terms and return them to the BAP. If overridden, the BAP must either agree to the terms sent by the BPP in order to preserve the provider's autonomy, or abort the transaction. In case of such disagreements, the BAP and the BPP can perform offline negotiations on the payment terms. Once an agreement is reached, the BAP and BPP can resume transactions.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "ID of the payment term that can be referred at an item or an order level in a catalog", - "type": "string" - }, - "collected_by": { - "description": "This field indicates who is the collector of payment. The BAP can set this value to 'bap' if it wants to collect the payment first and settle it to the BPP. If the BPP agrees to those terms, the BPP should not send the payment url. Alternatively, the BPP can set this field with the value 'bpp' if it wants the payment to be made directly.", - "type": "string" - }, - "url": { - "type": "string", - "description": "A payment url to be called by the BAP. If empty, then the payment is to be done offline. The details of payment should be present in the params object. If tl_method = http/get, then the payment details will be sent as url params. Two url param values, ```$transaction_id``` and ```$amount``` are mandatory.", - "format": "uri" - }, - "tl_method": { - "type": "string" - }, - "params": { - "type": "object", - "additionalProperties": false, - "properties": { - "transaction_id": { - "type": "string", - "description": "The reference transaction ID associated with a payment activity" - }, - "amount": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "bank_code": { - "type": "string" - }, - "bank_account_number": { - "type": "string" - }, - "virtual_payment_address": { - "type": "string" - }, - "source_bank_code": { - "type": "string" - }, - "source_bank_account_number": { - "type": "string" - }, - "source_virtual_payment_address": { - "type": "string" - } - } - }, - "type": { - "type": "string", - "enum": [ - "PRE-ORDER", - "PRE-FULFILLMENT", - "ON-FULFILLMENT", - "POST-FULFILLMENT", - "ON-ORDER", - "PART-PAYMENT" - ] - }, - "status": { - "type": "string", - "enum": [ - "PAID", - "NOT-PAID" - ] - }, - "time": { - "$ref": "definitions.json#/$defs/Time" - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "Person": { - "$id": "Person", - "description": "Describes a person as any individual", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "description": "Describes the identity of the person" - }, - "url": { - "description": "Profile url of the person", - "type": "string", - "format": "uri" - }, - "name": { - "description": "the name of the person", - "type": "string" - }, - "image": { - "$ref": "definitions.json#/$defs/Image" - }, - "age": { - "description": "Age of the person", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Duration" - } - ] - }, - "dob": { - "description": "Date of birth of the person", - "type": "string", - "format": "date" - }, - "gender": { - "type": "string", - "description": "Gender of something, typically a Person, but possibly also fictional characters, animals, etc. While Male and Female may be used, text strings are also acceptable for people who do not identify as a binary gender.Allowed values for this field can be published in the network policy" - }, - "creds": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Credential" - } - }, - "languages": { - "type": "array", - "items": { - "description": "Describes a language known to the person.", - "type": "object", - "additionalProperties": false, - "properties": { - "code": { - "type": "string" - }, - "name": { - "type": "string" - } - } - } - }, - "skills": { - "type": "array", - "items": { - "description": "Describes a skill of the person.", - "type": "object", - "additionalProperties": false, - "properties": { - "code": { - "type": "string" - }, - "name": { - "type": "string" - } - } - } - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "Price": { - "$id": "Price", - "description": "Describes the price of a product or service", - "type": "object", - "additionalProperties": false, - "properties": { - "currency": { - "type": "string" - }, - "value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "estimated_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "computed_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "listed_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "offered_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "minimum_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "maximum_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - } - } - }, - "Provider": { - "$id": "Provider", - "description": "Describes the catalog of a business.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "description": "Id of the provider" - }, - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "category_id": { - "type": "string", - "description": "Category Id of the provider at the BPP-level catalog" - }, - "rating": { - "$ref": "definitions.json#/$defs/Rating/properties/value" - }, - "time": { - "$ref": "definitions.json#/$defs/Time" - }, - "categories": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Category" - } - }, - "fulfillments": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Fulfillment" - } - }, - "payments": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Payment" - } - }, - "locations": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Location" - } - }, - "offers": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Offer" - } - }, - "items": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Item" - } - }, - "exp": { - "type": "string", - "description": "Time after which catalog has to be refreshed", - "format": "date-time" - }, - "rateable": { - "description": "Whether this provider can be rated or not", - "type": "boolean" - }, - "ttl": { - "description": "The time-to-live in seconds, for this object. This can be overriden at deeper levels. A value of -1 indicates that this object is not cacheable.", - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/TagGroup" - } - } - } - }, - "Quotation": { - "$id": "Quotation", - "description": "Describes a quote. It is the estimated price of products or services from the BPP.
This has properties like price, breakup, ttl", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "ID of the quote.", - "type": "string", - "format": "uuid" - }, - "price": { - "description": "The total quoted price", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Price" - } - ] - }, - "breakup": { - "description": "the breakup of the total quoted price", - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "item": { - "$ref": "definitions.json#/$defs/Item" - }, - "title": { - "type": "string" - }, - "price": { - "$ref": "definitions.json#/$defs/Price" - } - } - } - }, - "ttl": { - "$ref": "definitions.json#/$defs/Duration" - } - } - }, - "Rating": { - "$id": "Rating", - "description": "Describes the rating of an entity", - "type": "object", - "additionalProperties": false, - "properties": { - "rating_category": { - "description": "Category of the entity being rated", - "type": "string", - "enum": [ - "Item", - "Order", - "Fulfillment", - "Provider", - "Agent", - "Support" - ] - }, - "id": { - "description": "Id of the object being rated", - "type": "string" - }, - "value": { - "description": "Rating value given to the object. This can be a single value or can also contain an inequality operator like gt, gte, lt, lte. This can also contain an inequality expression containing logical operators like && and ||.", - "type": "string" - } - } - }, - "Region": { - "$id": "Region", - "description": "Describes an arbitrary region of space. The network policy should contain a published list of supported regions by the network.", - "type": "object", - "additionalProperties": false, - "properties": { - "dimensions": { - "description": "The number of dimensions that are used to describe any point inside that region. The most common dimensionality of a region is 2, that represents an area on a map. There are regions on the map that can be approximated to one-dimensional regions like roads, railway lines, or shipping lines. 3 dimensional regions are rarer, but are gaining popularity as flying drones are being adopted for various fulfillment services.", - "type": "string", - "enum": [ - "1", - "2", - "3" - ] - }, - "type": { - "description": "The type of region. This is used to specify the granularity of the region represented by this object. Various examples of two-dimensional region types are city, country, state, district, and so on. The network policy should contain a list of all possible region types supported by the network.", - "type": "string" - }, - "name": { - "type": "string", - "description": "Name of the region as specified on the map where that region exists." - }, - "code": { - "type": "string", - "description": "A standard code representing the region. This should be interpreted in the same way by all network participants." - }, - "boundary": { - "type": "string", - "description": "A string representing the boundary of the region. One-dimensional regions are represented by polylines. Two-dimensional regions are represented by polygons, and three-dimensional regions can represented by polyhedra." - }, - "map_url": { - "type": "string", - "description": "The url to the map of the region. This can be a globally recognized map or the one specified by the network policy." - } - } - }, - "ReplacementTerm": { - "$id": "ReplacementTerm", - "description": "The replacement policy of an item or an order", - "type": "object", - "additionalProperties": false, - "properties": { - "fulfillment_state": { - "description": "The state of fulfillment during which this term is applicable.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/State" - } - ] - }, - "replace_within": { - "description": "Applicable only for buyer managed returns where the buyer has to replace the item before a certain date-time, failing which they will not be eligible for replacement", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Time" - } - ] - }, - "external_ref": { - "$ref": "definitions.json#/$defs/MediaFile" - } - } - }, - "ReturnTerm": { - "$id": "ReturnTerm", - "description": "Describes the return policy of an item or an order", - "type": "object", - "additionalProperties": false, - "properties": { - "fulfillment_state": { - "description": "The state of fulfillment during which this term IETF''s applicable.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/State" - } - ] - }, - "return_eligible": { - "description": "Indicates whether the item is eligible for return", - "type": "boolean" - }, - "return_time": { - "description": "Applicable only for buyer managed returns where the buyer has to return the item to the origin before a certain date-time, failing which they will not be eligible for refund.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Time" - } - ] - }, - "return_location": { - "description": "The location where the item or order must / will be returned to", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Location" - } - ] - }, - "fulfillment_managed_by": { - "description": "The entity that will perform the return", - "type": "string", - "enum": [ - "CONSUMER", - "PROVIDER" - ] - } - } - }, - "Scalar": { - "$id": "Scalar", - "description": "Describes a scalar", - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": [ - "CONSTANT", - "VARIABLE" - ] - }, - "value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "estimated_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "computed_value": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "range": { - "type": "object", - "additionalProperties": false, - "properties": { - "min": { - "$ref": "definitions.json#/$defs/DecimalValue" - }, - "max": { - "$ref": "definitions.json#/$defs/DecimalValue" - } - } - }, - "unit": { - "type": "string" - } - } - }, - "Schedule": { - "$id": "Schedule", - "description": "Describes schedule as a repeating time period used to describe a regularly recurring event. At a minimum a schedule will specify frequency which describes the interval between occurrences of the event. Additional information can be provided to specify the schedule more precisely. This includes identifying the timestamps(s) of when the event will take place. Schedules may also have holidays to exclude a specific day from the schedule.
This has properties like frequency, holidays, times", - "type": "object", - "additionalProperties": false, - "properties": { - "frequency": { - "$ref": "definitions.json#/$defs/Duration" - }, - "holidays": { - "type": "array", - "items": { - "type": "string", - "format": "date-time" - } - }, - "times": { - "type": "array", - "items": { - "type": "string", - "format": "date-time" - } - } - } - }, - "State": { - "$id": "State", - "description": "A bounded geopolitical region of governance inside a country.", - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "Name of the state" - }, - "code": { - "type": "string", - "description": "State code as per country or international standards" - } - } - }, - "Stop": { - "$id": "Stop", - "description": "A logical point in space and time during the fulfillment of an order.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "parent_stop_id": { - "type": "string" - }, - "location": { - "description": "Location of the stop", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Location" - } - ] - }, - "type": { - "description": "The type of stop. Allowed values of this property can be defined by the network policy.", - "type": "string" - }, - "time": { - "description": "Timings applicable at the stop.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Time" - } - ] - }, - "instructions": { - "description": "Instructions that need to be followed at the stop", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Descriptor" - } - ] - }, - "contact": { - "description": "Contact details of the stop", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Contact" - } - ] - }, - "person": { - "description": "The details of the person present at the stop", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Person" - } - ] - }, - "authorization": { - "$ref": "definitions.json#/$defs/Authorization" - } - } - }, - "Support": { - "$id": "Support", - "description": "Details of customer support", - "type": "object", - "additionalProperties": false, - "properties": { - "ref_id": { - "type": "string" - }, - "callback_phone": { - "type": "string", - "format": "phone" - }, - "phone": { - "type": "string", - "format": "phone" - }, - "email": { - "type": "string", - "format": "email" - }, - "url": { - "type": "string", - "format": "uri" - } - } - }, - "Tag": { - "$id": "Tag", - "description": "Describes a tag. This is used to contain extended metadata. This object can be added as a property to any schema to describe extended attributes. For BAPs, tags can be sent during search to optimize and filter search results. BPPs can use tags to index their catalog to allow better search functionality. Tags are sent by the BPP as part of the catalog response in the `on_search` callback. Tags are also meant for display purposes. Upon receiving a tag, BAPs are meant to render them as name-value pairs. This is particularly useful when rendering tabular information about a product or service.", - "type": "object", - "additionalProperties": false, - "properties": { - "descriptor": { - "description": "Description of the Tag, can be used to store detailed information.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Descriptor" - } - ] - }, - "value": { - "description": "The value of the tag. This set by the BPP and rendered as-is by the BAP.", - "type": "string" - }, - "display": { - "description": "This value indicates if the tag is intended for display purposes. If set to `true`, then this tag must be displayed. If it is set to `false`, it should not be displayed. This value can override the group display value.", - "type": "boolean" - } - } - }, - "TagGroup": { - "$id": "TagGroup", - "description": "A collection of tag objects with group level attributes. For detailed documentation on the Tags and Tag Groups schema go to https://github.com/beckn/protocol-specifications/discussions/316", - "type": "object", - "additionalProperties": false, - "properties": { - "display": { - "description": "Indicates the display properties of the tag group. If display is set to false, then the group will not be displayed. If it is set to true, it should be displayed. However, group-level display properties can be overriden by individual tag-level display property. As this schema is purely for catalog display purposes, it is not recommended to send this value during search.", - "type": "boolean", - "default": true - }, - "descriptor": { - "description": "Description of the TagGroup, can be used to store detailed information.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Descriptor" - } - ] - }, - "list": { - "description": "An array of Tag objects listed under this group. This property can be set by BAPs during search to narrow the `search` and achieve more relevant results. When received during `on_search`, BAPs must render this list under the heading described by the `name` property of this schema.", - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Tag" - } - } - } - }, - "Time": { - "$id": "Time", - "description": "Describes time in its various forms. It can be a single point in time; duration; or a structured timetable of operations
This has properties like label, time stamp,duration,range, days, schedule", - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "string" - }, - "timestamp": { - "type": "string", - "format": "date-time" - }, - "duration": { - "$ref": "definitions.json#/$defs/Duration" - }, - "range": { - "type": "object", - "additionalProperties": false, - "properties": { - "start": { - "type": "string", - "format": "date-time" - }, - "end": { - "type": "string", - "format": "date-time" - } - } - }, - "days": { - "type": "string", - "description": "comma separated values representing days of the week" - }, - "schedule": { - "$ref": "definitions.json#/$defs/Schedule" - } - } - }, - "Tracking": { - "$id": "Tracking", - "description": "Contains tracking information that can be used by the BAP to track the fulfillment of an order in real-time. which is useful for knowing the location of time sensitive deliveries.", - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "description": "A unique tracking reference number", - "type": "string" - }, - "url": { - "description": "A URL to the tracking endpoint. This can be a link to a tracking webpage, a webhook URL created by the BAP where BPP can push the tracking data, or a GET url creaed by the BPP which the BAP can poll to get the tracking data. It can also be a websocket URL where the BPP can push real-time tracking data.", - "type": "string", - "format": "uri" - }, - "location": { - "description": "In case there is no real-time tracking endpoint available, this field will contain the latest location of the entity being tracked. The BPP will update this value everytime the BAP calls the track API.", - "allOf": [ - { - "$ref": "definitions.json#/$defs/Location" - } - ] - }, - "status": { - "description": "This value indicates if the tracking is currently active or not. If this value is `active`, then the BAP can begin tracking the order. If this value is `inactive`, the tracking URL is considered to be expired and the BAP should stop tracking the order.", - "type": "string", - "enum": [ - "active", - "inactive" - ] - } - } - }, - "Vehicle": { - "$id": "Vehicle", - "description": "Describes a vehicle is a device that is designed or used to transport people or cargo over land, water, air, or through space.
This has properties like category, capacity, make, model, size,variant,color,energy_type,registration", - "type": "object", - "additionalProperties": false, - "properties": { - "category": { - "type": "string" - }, - "capacity": { - "type": "integer" - }, - "make": { - "type": "string" - }, - "model": { - "type": "string" - }, - "size": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "color": { - "type": "string" - }, - "energy_type": { - "type": "string" - }, - "registration": { - "type": "string" - }, - "wheels_count": { - "type": "string" - }, - "cargo_volumne": { - "type": "string" - }, - "wheelchair_access": { - "type": "string" - }, - "code": { - "type": "string" - }, - "emission_standard": { - "type": "string" - } - } - }, - "XInput": { - "$id": "XInput", - "description": "Contains any additional or extended inputs required to confirm an order. This is typically a Form Input. Sometimes, selection of catalog elements is not enough for the BPP to confirm an order. For example, to confirm a flight ticket, the airline requires details of the passengers along with information on baggage, identity, in addition to the class of ticket. Similarly, a logistics company may require details on the nature of shipment in order to confirm the shipping. A recruiting firm may require additional details on the applicant in order to confirm a job application. For all such purposes, the BPP can choose to send this object attached to any object in the catalog that is required to be sent while placing the order. This object can typically be sent at an item level or at the order level. The item level XInput will override the Order level XInput as it indicates a special requirement of information for that particular item. Hence the BAP must render a separate form for the Item and another form at the Order level before confirmation.", - "type": "object", - "additionalProperties": false, - "properties": { - "head": { - "description": "Provides the header information for the xinput.", - "type": "object", - "additionalProperties": false, - "properties": { - "descriptor": { - "$ref": "definitions.json#/$defs/Descriptor" - }, - "index": { - "type": "object", - "additionalProperties": false, - "properties": { - "min": { - "type": "integer" - }, - "cur": { - "type": "integer" - }, - "max": { - "type": "integer" - } - } - }, - "headings": { - "type": "array", - "items": { - "type": "string", - "description": "The heading names of the forms" - } - } - } - }, - "form": { - "$ref": "definitions.json#/$defs/Form" - }, - "form_response": { - "description": "Describes the response to a form submission", - "type": "object", - "additionalProperties": false, - "properties": { - "status": { - "description": "Contains the status of form submission.", - "type": "string" - }, - "signature": { - "type": "string" - }, - "submission_id": { - "type": "string" - }, - "errors": { - "type": "array", - "items": { - "$ref": "definitions.json#/$defs/Error" - } - } - } - }, - "required": { - "description": "Indicates whether the form data is mandatorily required by the BPP to confirm the order.", - "type": "boolean" - } - } - } - } - } \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_cancel.json b/config/onix/schemas/core/v1.1.0/on_cancel.json deleted file mode 100644 index 4abb380..0000000 --- a/config/onix/schemas/core/v1.1.0/on_cancel.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_cancel", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_cancel" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context", - "message" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_confirm.json b/config/onix/schemas/core/v1.1.0/on_confirm.json deleted file mode 100644 index 4abea80..0000000 --- a/config/onix/schemas/core/v1.1.0/on_confirm.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_confirm", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_confirm" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_init.json b/config/onix/schemas/core/v1.1.0/on_init.json deleted file mode 100644 index 18c44f3..0000000 --- a/config/onix/schemas/core/v1.1.0/on_init.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_init", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_init" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_rating.json b/config/onix/schemas/core/v1.1.0/on_rating.json deleted file mode 100644 index 0b9898b..0000000 --- a/config/onix/schemas/core/v1.1.0/on_rating.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_rating", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_rating" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "feedback_form": { - "description": "A feedback form to allow the user to provide additional information on the rating provided", - "allOf": [ - { - "$ref": "./definitions.json#/$defs/XInput" - } - ] - } - } - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context", - "message" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_search.json b/config/onix/schemas/core/v1.1.0/on_search.json deleted file mode 100644 index 854c6f9..0000000 --- a/config/onix/schemas/core/v1.1.0/on_search.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_search", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_search" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "catalog": { - "$ref": "./definitions.json#/$defs/Catalog" - } - }, - "required": [ - "catalog" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_select.json b/config/onix/schemas/core/v1.1.0/on_select.json deleted file mode 100644 index 956c6a8..0000000 --- a/config/onix/schemas/core/v1.1.0/on_select.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_select", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_select" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - } - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_status.json b/config/onix/schemas/core/v1.1.0/on_status.json deleted file mode 100644 index 00167e5..0000000 --- a/config/onix/schemas/core/v1.1.0/on_status.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_status", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_status" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_support.json b/config/onix/schemas/core/v1.1.0/on_support.json deleted file mode 100644 index 7ba94f0..0000000 --- a/config/onix/schemas/core/v1.1.0/on_support.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_support", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_support" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "support": { - "$ref": "./definitions.json#/$defs/Support" - } - } - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context", - "message" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_track.json b/config/onix/schemas/core/v1.1.0/on_track.json deleted file mode 100644 index cbaf906..0000000 --- a/config/onix/schemas/core/v1.1.0/on_track.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_track", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_track" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "tracking": { - "$ref": "./definitions.json#/$defs/Tracking" - } - }, - "required": [ - "tracking" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "context", - "message" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/on_update.json b/config/onix/schemas/core/v1.1.0/on_update.json deleted file mode 100644 index 74de03c..0000000 --- a/config/onix/schemas/core/v1.1.0/on_update.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_update", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "on_update" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - }, - "error": { - "$ref": "./definitions.json#/$defs/Error" - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/search.json b/config/onix/schemas/core/v1.1.0/search.json deleted file mode 100644 index 7faae89..0000000 --- a/config/onix/schemas/core/v1.1.0/search.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "search", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - } - ] - }, - "message": { - "properties": { - "intent": { - "$ref": "./definitions.json#/$defs/Intent" - } - }, - "type": "object" - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/core/v1.1.0/select.json b/config/onix/schemas/core/v1.1.0/select.json deleted file mode 100644 index 7151fff..0000000 --- a/config/onix/schemas/core/v1.1.0/select.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "select", - "type": "object", - "properties": { - "context": { - "allOf": [ - { - "$ref": "./definitions.json#/$defs/Context" - }, - { - "type": "object", - "properties": { - "action": { - "enum": [ - "select" - ] - } - }, - "required": [ - "action" - ] - } - ] - }, - "message": { - "type": "object", - "additionalProperties": false, - "properties": { - "order": { - "$ref": "./definitions.json#/$defs/Order" - } - }, - "required": [ - "order" - ] - } - }, - "required": [ - "message", - "context" - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/cancel.json b/config/onix/schemas/ondc_trv10/v2.0.0/cancel.json deleted file mode 100644 index b53fedb..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/cancel.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "cancel", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/cancel.json#" - }, - { - "$ref": "init.json#/allOf/2" - }, - { - "properties": { - "message": { - "type": "object", - "properties": { - "order_id": { - "type": "string" - }, - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": ["SOFT_CANCEL", "CONFIRM_CANCEL"] - } - }, - "required": ["code"] - }, - "cancellation_reason_id": { - "type": "string", - "pattern": "^[0-9]+$" - } - }, - "required": ["order_id", "descriptor", "cancellation_reason_id"] - } - } - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/confirm.json b/config/onix/schemas/ondc_trv10/v2.0.0/confirm.json deleted file mode 100644 index 25f4e01..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/confirm.json +++ /dev/null @@ -1,463 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "confirm", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/confirm.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "$ref": "./init.json#/allOf/2" - }, - { - "allOf": [ - { - "$ref": "./on_select.json#/allOf/5" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "items": { - "allOf": [ - { - "properties": { - "customer": { - "properties": { - "contact": { - "properties": { - "phone": { - "type": "string", - "pattern": "^\\+?[1-9]\\d{1,14}$" - } - }, - "required": [ - "phone" - ] - }, - "person": { - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ] - } - }, - "required": [ - "contact", - "person" - ] - } - }, - "required": [ - "customer" - ] - } - ] - } - } - } - } - } - } - } - } - ] - }, - { - "allOf": [ - { - "$ref": "./init.json#/allOf/7" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "payments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "params": { - "type": "object", - "properties": { - "amount": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - }, - "type": { - "type": "string", - "enum": [ - "PRE-ORDER", - "ON-FULFILLMENT", - "POST-FULFILLMENT" - ] - }, - "status": { - "type": "string", - "enum": [ - "PAID", - "NOT-PAID" - ] - }, - "collected_by": { - "type": "string", - "enum": [ - "BAP", - "BPP" - ] - }, - "tags": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": [ - "SETTLEMENT_TERMS", - "BUYER_FINDER_FEES" - ] - } - }, - "allOf": [ - { - "if": { - "properties": { - "descriptor": { - "properties": { - "code": { - "const": "SETTLEMENT_TERMS" - } - } - } - } - }, - "then": { - "properties": { - "list": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "STATIC_TERMS" - } - } - }, - "value": { - "type": "string", - "format": "uri" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_BASIS" - } - } - }, - "value": { - "type": "string", - "enum": [ - "DELIVERY" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_WINDOW" - } - } - }, - "value": { - "type": "string" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "DELAY_INTEREST" - } - } - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_TYPE" - } - } - }, - "value": { - "type": "string", - "enum": [ - "upi", - "neft", - "rtgs", - "UPI", - "NEFT", - "RTGS" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_AMOUNT" - } - } - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "MANDATORY_ARBITRATION" - } - } - }, - "value": { - "type": "string", - "enum": [ - "true", - "false" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "COURT_JURISDICTION" - } - } - }, - "value": { - "type": "string" - } - } - } - } - ] - } - } - } - }, - { - "if": { - "properties": { - "descriptor": { - "properties": { - "code": { - "const": "BUYER_FINDER_FEES" - } - } - } - } - }, - "then": { - "properties": { - "list": { - "type": "array", - "items": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "enum": [ - "BUYER_FINDER_FEES_PERCENTAGE" - ] - } - } - }, - "value": { - "type": "string", - "pattern": "^-?\\d+(\\.\\d+)?$" - } - } - } - } - } - } - } - ] - } - }, - "required": [ - "descriptor" - ] - } - } - }, - "required": [ - "type", - "status", - "collected_by", - "tags" - ] - } - } - } - } - } - } - } - } - ] - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "payments": { - "type": "array", - "items": { - "properties": { - "type": { - "type": "string" - }, - "params": { - "type": "object", - "properties": { - "transaction_id": { - "type": "string" - } - } - } - }, - "required": [ - "type" - ], - "allOf": [ - { - "if": { - "properties": { - "type": { - "const": "PRE-ORDER" - } - } - }, - "then": { - "properties": { - "params": { - "required": [ - "transaction_id" - ] - } - } - } - } - ] - } - } - }, - "required": [ - "payments" - ] - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "not": { - "required": [ - "id" - ] - } - } - } - } - } - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/init.json b/config/onix/schemas/ondc_trv10/v2.0.0/init.json deleted file mode 100644 index 395ba67..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/init.json +++ /dev/null @@ -1,550 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "init", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/init.json#" - }, - { - "properties": { - "context": { - "type": "object", - "allOf": [ - { - "$ref": "./search.json#/properties/context/allOf/0" - }, - { - - "required": [ - "bpp_id", - "bpp_uri" - ] - } - ] - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "type": "object", - "properties": { - "provider": { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": [ - "id" - ] - }, - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": [ - "id" - ] - } - } - }, - "required": [ - "provider", - "items" - ] - } - } - } - } - }, - { - "$ref": "./confirm.json#/allOf/4" - }, - { - "$ref": "./on_select.json#/allOf/7" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "required": [ - "fulfillments" - ] - } - } - } - } - }, - { - "properties": { - "message": { - "type": "object", - "properties": { - "order": { - "type": "object", - "properties": { - "payments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "params": { - "type": "object", - "properties": { - "amount": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - }, - "type": { - "type": "string", - "enum": [ - "PRE-ORDER", - "ON-FULFILLMENT", - "POST-FULFILLMENT" - ] - }, - "status": { - "type": "string", - "enum": [ - "PAID", - "NOT-PAID" - ] - }, - "collected_by": { - "type": "string", - "enum": [ - "BAP", - "BPP" - ] - }, - "tags": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": [ - "SETTLEMENT_TERMS", - "BUYER_FINDER_FEES" - ] - } - } - } - }, - "required": [ - "descriptor" - ] - } - } - }, - "required": [ - "type", - "status", - "collected_by", - "tags" - ], - "allOf": [ - { - "if": { - "allOf": [ - { - "properties": { - "collected_by": { - "const": "BAP" - } - } - }, - { - "properties": { - "type": { - "const": "PRE-ORDER" - } - } - } - ] - }, - "then": { - "properties": { - "tags": { - "items": { - "if": { - "properties": { - "descriptor": { - "properties": { - "code": { - "const": "SETTLEMENT_TERMS" - } - } - } - } - }, - "then": { - "properties": { - "list": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "STATIC_TERMS" - } - } - }, - "value": { - "type": "string", - "format": "uri" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_BASIS" - } - } - }, - "value": { - "type": "string", - "enum": [ - "DELIVERY" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_WINDOW" - } - } - }, - "value": { - "type": "string" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "DELAY_INTEREST" - } - } - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - } - } - ] - } - } - } - } - } - } - } - }, - { - "if": { - "allOf": [ - { - "properties": { - "collected_by": { - "const": "BPP" - } - } - }, - { - "properties": { - "type": { - "const": "PRE-ORDER" - } - } - } - ] - }, - "then": { - "properties": { - "tags": { - "items": { - "if": { - "properties": { - "descriptor": { - "properties": { - "code": { - "const": "SETTLEMENT_TERMS" - } - } - } - } - }, - "then": { - "properties": { - "list": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "STATIC_TERMS" - } - } - }, - "value": { - "type": "string", - "format": "uri" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_BASIS" - } - } - }, - "value": { - "type": "string", - "enum": [ - "DELIVERY" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_WINDOW" - } - } - }, - "value": { - "type": "string" - } - } - } - } - ] - } - } - } - } - } - } - } - }, - { - "if": { - "allOf": [ - { - "properties": { - "collected_by": { - "const": "BPP" - } - } - }, - { - "properties": { - "type": { - "const": "ON-FULFILLMENT" - } - } - } - ] - }, - "then": { - "properties": { - "tags": { - "items": { - "if": { - "properties": { - "descriptor": { - "properties": { - "code": { - "const": "SETTLEMENT_TERMS" - } - } - } - } - }, - "then": { - "properties": { - "list": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "STATIC_TERMS" - } - } - }, - "value": { - "type": "string", - "format": "uri" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_BASIS" - } - } - }, - "value": { - "type": "string", - "enum": [ - "DELIVERY" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_WINDOW" - } - } - }, - "value": { - "type": "string" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "DELAY_INTEREST" - } - } - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - } - } - ] - } - } - } - } - } - } - } - } - ] - } - } - } - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "billing": { - "required": [ - "name" - ] - } - }, - "required": [ - "billing" - ] - } - } - } - } - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json deleted file mode 100644 index dbcf8f0..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_cancel.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_cancel", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_cancel.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json deleted file mode 100644 index 4a0083e..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_confirm.json +++ /dev/null @@ -1,314 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_confirm", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_confirm.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "provider": { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": ["id"] - } - }, - "required": ["provider"] - } - } - } - } - }, - { - "$ref": "./confirm.json#/allOf/5" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "items": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "fulfillment_ids": { - "minItems": 1 - }, - "location_ids": { - "minItems": 1 - } - }, - "required": ["fulfillment_ids", "location_ids"] - } - } - } - } - } - } - } - }, - { - "$ref": "./confirm.json#/allOf/4/allOf/1" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["DELIVERY"] - } - }, - "required": ["type"] - } - } - } - } - } - } - } - }, - { - "allOf": [ - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "quote": { - "type": "object", - "properties": { - "price": { - "type": "object", - "properties": { - "currency": { - "type": "string" - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - }, - "required": ["currency", "value"] - }, - "breakup": { - "type": "array", - "items": { - "type": "object", - "properties": { - "price": { - "type": "object", - "properties": { - "currency": { - "type": "string" - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - }, - "required": ["currency", "value"] - }, - "title": { - "type": "string", - "enum": [ - "BASE_FARE", - "DISTANCE_FARE", - "TAX", - "DISCOUNT", - "WAITING_CHARGE" - ] - } - }, - "required": ["price", "title"] - } - } - }, - "required": ["price", "breakup"] - } - }, - "required": ["quote"] - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "quote": { - "properties": { - "breakup": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "title": { - "const": "BASE_FARE" - }, - "price": { - "type": "object", - "properties": { - "value": { - "type": "string" - } - }, - "required": ["value"] - } - }, - "required": ["title", "price"] - } - }, - { - "contains": { - "type": "object", - "properties": { - "title": { - "const": "DISTANCE_FARE" - }, - "price": { - "type": "object", - "properties": { - "value": { - "type": "string" - } - }, - "required": ["value"] - } - }, - "required": ["title", "price"] - } - } - ] - } - } - } - } - } - } - } - } - } - ] - }, - { - "$ref": "./confirm.json#/allOf/6" - }, - { - "allOf": [ - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "cancellation_terms": { - "type": "array", - "items": { - "type": "object", - "properties": { - "fulfillment_state": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": [ - "RIDE_ASSIGNED", - "RIDE_ENROUTE_PICKUP", - "RIDE_ARRIVED_PICKUP", - "RIDE_STARTED" - ] - } - }, - "required": ["code"] - } - }, - "required": ["descriptor"] - }, - "cancellation_fee": { - "oneOf": [ - { - "type": "object", - "properties": { - "percentage": { - "type": "string", - "pattern": "^(100(\\.0{1,2})?|([0-9]{1,2})(\\.\\d{1,2})?)$" - } - }, - "required": ["percentage"] - }, - { - "type": "object", - "properties": { - "amount": { - "type": "object", - "properties": { - "value": { - "type": "string", - "pattern": "^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)$" - }, - "currency": { - "type": "string" - } - }, - "required": ["currency", "value"] - } - }, - "required": ["amount"] - } - ] - } - }, - "required": ["fulfillment_state", "cancellation_fee"] - } - } - }, - "required": ["cancellation_terms"] - } - } - } - } - }, - { - "properties": { - "message": { - "type": "object" - } - }, - "required": ["message"] - } - ] - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_init.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_init.json deleted file mode 100644 index fe7105c..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_init.json +++ /dev/null @@ -1,317 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_init", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_init.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "provider": { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": ["id"] - } - }, - "required": ["provider"] - } - } - } - } - }, - { - "$ref": "./confirm.json#/allOf/5" - }, - { - "$ref": "./on_select.json#/allOf/6" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "items": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "fulfillment_ids": { - "minItems": 1 - }, - "location_ids": { - "minItems": 1 - } - }, - "required": ["fulfillment_ids", "location_ids"] - } - } - } - } - } - } - } - }, - { - "$ref": "./confirm.json#/allOf/4/allOf/1" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["DELIVERY"] - } - }, - "required": ["type"] - } - } - } - } - } - } - } - }, - { - "allOf": [ - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "quote": { - "type": "object", - "properties": { - "price": { - "type": "object", - "properties": { - "currency": { - "type": "string" - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - }, - "required": ["currency", "value"] - }, - "breakup": { - "type": "array", - "items": { - "type": "object", - "properties": { - "price": { - "type": "object", - "properties": { - "currency": { - "type": "string" - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - }, - "required": ["currency", "value"] - }, - "title": { - "type": "string", - "enum": [ - "BASE_FARE", - "DISTANCE_FARE", - "TAX", - "DISCOUNT", - "WAITING_CHARGE" - ] - } - }, - "required": ["price", "title"] - } - } - }, - "required": ["price", "breakup"] - } - }, - "required": ["quote"] - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "quote": { - "properties": { - "breakup": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "title": { - "const": "BASE_FARE" - }, - "price": { - "type": "object", - "properties": { - "value": { - "type": "string" - } - }, - "required": ["value"] - } - }, - "required": ["title", "price"] - } - }, - { - "contains": { - "type": "object", - "properties": { - "title": { - "const": "DISTANCE_FARE" - }, - "price": { - "type": "object", - "properties": { - "value": { - "type": "string" - } - }, - "required": ["value"] - } - }, - "required": ["title", "price"] - } - } - ] - } - } - } - } - } - } - } - } - } - ] - }, - { - "$ref": "./confirm.json#/allOf/6" - }, - { - "allOf": [ - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "cancellation_terms": { - "type": "array", - "items": { - "type": "object", - "properties": { - "fulfillment_state": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": [ - "RIDE_ASSIGNED", - "RIDE_ENROUTE_PICKUP", - "RIDE_ARRIVED_PICKUP", - "RIDE_STARTED" - ] - } - }, - "required": ["code"] - } - }, - "required": ["descriptor"] - }, - "cancellation_fee": { - "oneOf": [ - { - "type": "object", - "properties": { - "percentage": { - "type": "string", - "pattern": "^(100(\\.0{1,2})?|([0-9]{1,2})(\\.\\d{1,2})?)$" - } - }, - "required": ["percentage"] - }, - { - "type": "object", - "properties": { - "amount": { - "type": "object", - "properties": { - "value": { - "type": "string", - "pattern": "^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)$" - }, - "currency": { - "type": "string" - } - }, - "required": ["currency", "value"] - } - }, - "required": ["amount"] - } - ] - } - }, - "required": ["fulfillment_state", "cancellation_fee"] - } - } - }, - "required": ["cancellation_terms"] - } - } - } - } - }, - { - "properties": { - "message": { - "type": "object" - } - }, - "required": ["message"] - } - ] - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json deleted file mode 100644 index 684a1c3..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_rating.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_rating", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_rating.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_search.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_search.json deleted file mode 100644 index 06d5a8b..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_search.json +++ /dev/null @@ -1,644 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_search", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_search.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "properties": { - "message": { - "type": "object", - "properties": { - "catalog": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "images": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1 - } - }, - "required": [ - "name" - ] - }, - "providers": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "descriptor": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "images": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1 - } - }, - "required": [ - "name" - ] - }, - "fulfillments": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "vehicle": { - "type": "object", - "properties": { - "category": { - "type": "string", - "enum": [ - "AUTO_RICKSHAW", - "CAB" - ] - } - }, - "required": [ - "category" - ] - }, - "type": { - "type": "string", - "enum": [ - "DELIVERY" - ] - } - }, - "required": [ - "id", - "vehicle", - "type" - ] - } - }, - "items": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "code": { - "type": "string", - "enum": [ - "RIDE" - ] - } - }, - "required": [ - "code" - ] - }, - "price": { - "type": "object", - "properties": { - "value": { - "type": "string", - "pattern": "^-?\\d+(\\.\\d+)?$" - } - }, - "required": [ - "value", - "currency" - ] - }, - "fulfillment_ids": { - "type": "array", - "minItems": 1 - }, - "payment_ids": { - "type": "array", - "minItems": 1 - } - }, - "required": [ - "id", - "descriptor", - "price", - "fulfillment_ids" - ] - } - } - }, - "required": [ - "id", - "items", - "fulfillments" - ] - } - } - }, - "required": [ - "providers" - ] - } - }, - "required": [ - "catalog" - ] - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "payments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "collected_by": { - "type": "string", - "enum": [ - "BAP", - "BPP" - ] - }, - "tags": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": [ - "SETTLEMENT_TERMS", - "BUYER_FINDER_FEES" - ] - } - } - } - }, - "required": [ - "descriptor" - ] - } - } - }, - "required": [ - "collected_by", - "tags" - ], - "allOf": [ - { - "if": { - "allOf": [ - { - "properties": { - "collected_by": { - "const": "BAP" - } - } - } - ] - }, - "then": { - "properties": { - "tags": { - "items": { - "if": { - "properties": { - "descriptor": { - "properties": { - "code": { - "const": "SETTLEMENT_TERMS" - } - } - } - } - }, - "then": { - "properties": { - "list": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "STATIC_TERMS" - } - } - }, - "value": { - "type": "string", - "format": "uri" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_TYPE" - } - } - }, - "value": { - "type": "string", - "enum": [ - "upi", - "neft", - "rtgs", - "UPI", - "NEFT", - "RTGS" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "DELAY_INTEREST" - } - } - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "MANDATORY_ARBITRATION" - } - } - }, - "value": { - "type": "string", - "enum": [ - "true", - "false" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "COURT_JURISDICTION" - } - } - }, - "value": { - "type": "string" - } - } - } - } - ] - } - } - } - } - } - } - } - }, - { - "if": { - "allOf": [ - { - "properties": { - "collected_by": { - "const": "BPP" - } - } - } - ] - }, - "then": { - "properties": { - "tags": { - "items": { - "if": { - "properties": { - "descriptor": { - "properties": { - "code": { - "const": "SETTLEMENT_TERMS" - } - } - } - } - }, - "then": { - "properties": { - "list": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "STATIC_TERMS" - } - } - }, - "value": { - "type": "string", - "format": "uri" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_TYPE" - } - } - }, - "value": { - "type": "string", - "enum": [ - "upi", - "neft", - "rtgs", - "UPI", - "NEFT", - "RTGS" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "DELAY_INTEREST" - } - } - }, - "value": { - "type": "string", - "pattern": "^\\d+(\\.\\d{1,2})?$" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "MANDATORY_ARBITRATION" - } - } - }, - "value": { - "type": "string", - "enum": [ - "true", - "false" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "COURT_JURISDICTION" - } - } - }, - "value": { - "type": "string" - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_BASIS" - } - } - }, - "value": { - "type": "string", - "enum": [ - "DELIVERY" - ] - } - } - } - }, - { - "contains": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "const": "SETTLEMENT_WINDOW" - } - } - }, - "value": { - "type": "string" - } - } - } - } - ] - } - } - } - } - } - } - } - } - ] - } - } - }, - "required": [ - "payments" - ] - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "catalog": { - "properties": { - "providers": { - "items": { - "properties": { - "fulfillments": { - "items": { - "properties": { - "stops": { - "allOf": [ - { - "contains": { - "type": "object", - "properties": { - "location": { - "type": "object", - "properties": { - "gps": { - "type": "string" - } - }, - "required": [ - "gps" - ] - }, - "type": { - "const": "START" - } - }, - "required": [ - "location", - "type" - ] - } - }, - { - "contains": { - "type": "object", - "properties": { - "location": { - "type": "object", - "properties": { - "gps": { - "type": "string" - } - }, - "required": [ - "gps" - ] - }, - "type": { - "const": "END" - } - }, - "required": [ - "location", - "type" - ] - } - } - ] - } - }, - "required": [ - "stops" - ] - } - } - } - } - } - } - } - } - } - } - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_select.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_select.json deleted file mode 100644 index 9ac80b7..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_select.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_select", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_select.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "$ref": "./on_init.json#/allOf/2" - }, - { - "$ref": "./confirm.json#/allOf/5" - }, - { - "$ref": "./on_init.json#/allOf/5" - }, - { - "allOf": [ - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "minItems": 1, - "items": { - "required": ["id"] - } - } - }, - "required": ["fulfillments"] - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "state": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": [ - "RIDE_ASSIGNED", - "RIDE_ENROUTE_PICKUP", - "RIDE_ARRIVED_PICKUP", - "RIDE_STARTED", - "RIDE_ENDED", - "RIDE_CANCELLED" - ] - } - }, - "required": ["code"] - } - } - } - } - } - } - } - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "minItems": 1, - "items": { - "properties": { - "stops": { - "items": { - "properties": { - "authorization": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["OTP"] - }, - "token": { - "type": "string", - "pattern": "^-?\\d+(\\.\\d+)?$" - } - }, - "required": ["type", "token"] - } - } - } - } - } - } - } - } - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "items": { - "properties": { - "stops": { - "type": "array", - "minItems": 2, - "items": { - "type": "object", - "properties": { - "location": { - "type": "object", - "properties": { - "gps": { "type": "string" } - }, - "required": ["gps"] - }, - "type": { - "enum": ["START", "END"] - } - }, - "required": ["location", "type"] - } - } - }, - "required": ["stops"] - } - } - } - } - } - } - } - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "vehicle": { - "properties": { - "category": { - "type": "string", - "enum": ["AUTO_RICKSHAW", "CAB"] - } - }, - "required": ["category"] - } - }, - "required": ["vehicle"] - } - } - }, - "required": ["fulfillments"] - } - } - } - } - } - ] - }, - { - "$ref": "./on_init.json#/allOf/7" - }, - { - "properties": { - "message": { - "properties": { - "order": { - "properties": { - "fulfillments": { - "type": "array", - "items": { - "allOf": [ - { - "not": { - "required": ["agent"] - } - } - ] - } - } - } - } - } - } - } - }, - { - "$ref": "./on_init.json#/allOf/8" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_status.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_status.json deleted file mode 100644 index 3ac9a6c..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_status.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_status", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_status.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_support.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_support.json deleted file mode 100644 index 5884c85..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_support.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_support", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_support.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_track.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_track.json deleted file mode 100644 index 7796a66..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_track.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_track", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_track.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/on_update.json b/config/onix/schemas/ondc_trv10/v2.0.0/on_update.json deleted file mode 100644 index 487def2..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/on_update.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "on_update", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/on_update.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/rating.json b/config/onix/schemas/ondc_trv10/v2.0.0/rating.json deleted file mode 100644 index 5cfbc74..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/rating.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "rating", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/rating.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/search.json b/config/onix/schemas/ondc_trv10/v2.0.0/search.json deleted file mode 100644 index f661f8e..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/search.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "search", - "allOf": [ - { "$ref": "../../core/v1.1.0/search.json#" } - ], - "type": "object", - "properties": { - "context": { - "type": "object", - "allOf": [ - { - "properties": { - "action": { - "type": "string" - }, - "location": { - "type": "object", - "properties": { - "city": { - "type": "object", - "properties": { - "code": { "type": "string" } - }, - "required": ["code"] - }, - "country": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": ["IND"] - } - }, - "required": ["code"] - } - }, - "required": ["city", "country"] - }, - "bap_id": { - "type": "string" - }, - "bpp_id": { - "type": "string" - }, - "ttl": { - "type": "string", - "format": "duration" - }, - "timestamp": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "location", - "domain", - "action", - "message_id", - "transaction_id", - "timestamp", - "bap_id", - "bap_uri", - "ttl" - ] - } - ] - }, - "message": { - "type": "object", - "properties": { - "intent": { - "allOf": [ - { - "type": "object", - "properties": { - "payment": { - "type": "object", - "properties": { - "collected_by": { - "type": "string", - "enum": ["BPP", "BAP"] - }, - "tags": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "uniqueItems": true, - "items": { - "type": "object", - "properties": { - "descriptor": { - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": ["SETTLEMENT_TERMS", "BUYER_FINDER_FEES"] - } - } - } - }, - "required": ["descriptor"] - } - } - }, - "required": ["collected_by"] - }, - "fulfillment": { - "type": "object", - "properties": { - "stops": { - "type": "array", - "items": { - "type": "object", - "properties": { - "location": { - "type": "object", - "properties": { - "gps": { "type": "string" } - }, - "required": ["gps"] - }, - "type": { - "type": "string", - "enum": ["START", "END"] - } - }, - "required": ["location", "type"] - }, - "minItems": 2 - } - }, - "required": ["stops"] - } - }, - "required": ["payment", "fulfillment"] - } - ] - } - }, - "required": ["intent"] - } - }, - "required": ["context", "message"] - } - \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/select.json b/config/onix/schemas/ondc_trv10/v2.0.0/select.json deleted file mode 100644 index 2d22396..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/select.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "select", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/select.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "$ref": "./init.json#/allOf/2" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/status.json b/config/onix/schemas/ondc_trv10/v2.0.0/status.json deleted file mode 100644 index a9085d2..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/status.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "status", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/status.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "properties": { - "message": { - "type": "object", - "properties": { - "order_id": { - "type": "string" - } - }, - "required": ["order_id"] - } - } - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/support.json b/config/onix/schemas/ondc_trv10/v2.0.0/support.json deleted file mode 100644 index de4dbda..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/support.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "support", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/support.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/track.json b/config/onix/schemas/ondc_trv10/v2.0.0/track.json deleted file mode 100644 index 242cec1..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/track.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "track", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/track.json#" - }, - { - "$ref": "./init.json#/allOf/1" - } - ] -} \ No newline at end of file diff --git a/config/onix/schemas/ondc_trv10/v2.0.0/update.json b/config/onix/schemas/ondc_trv10/v2.0.0/update.json deleted file mode 100644 index d197cc7..0000000 --- a/config/onix/schemas/ondc_trv10/v2.0.0/update.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "update", - "type": "object", - "allOf": [ - { - "$ref": "../../core/v1.1.0/update.json#" - }, - { - "$ref": "./init.json#/allOf/1" - }, - { - "properties": { - "message": { - "type": "object", - "properties": { - "order": { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": ["id"] - }, - "update_target": { - "type": "string", - "pattern": "^[^,]+(,[^,]+)*$" - } - } - } - } - } - ] -} \ No newline at end of file diff --git a/config/sellerData.yaml b/config/sellerData.yaml deleted file mode 100644 index 9a96fdf..0000000 --- a/config/sellerData.yaml +++ /dev/null @@ -1,69 +0,0 @@ -- id: pooja-stores - descriptor: - name: Pooja Stores - locations: - - id: koramangala-4th-block-location - gps: "12.9349377,77.6055586" - categories: - - id: fresh_fruits - descriptor: - name: Fresh Fruits - - id: beverages - descriptor: - name: Beverages - items: - - id: item_2 - descriptor: - name: Green Apples Organic - images: - - url: "https://mock_bpp.com/images/item_2.jpg" - category_id: fresh_fruits - location_id: koramangala-4th-block-location - price: - currency: INR - value: "170" - matched: true - - id: item_1 - descriptor: - name: Red Apples - images: - - url: "https://mock_bpp.com/images/item_1.jpg" - category_id: fresh_fruits - location_id: koramangala-4th-block-location - price: - currency: INR - value: "90" - related: true - - id: item_7 - descriptor: - name: Green Apple Juice - images: - - url: "https://mock_bpp.com/images/item_7.jpg" - category_id: beverages - location_id: koramangala-4th-block-location - price: - currency: INR - value: "70" - matched: true -- id: food-mall - descriptor: - name: Food Mall - locations: - - id: food-mall-location - gps: "12.9349377,77.6055586" - categories: - - id: fresh-food - descriptor: - name: Fresh food - items: - - id: item_1_1 - descriptor: - name: Green Apple Salad - images: - - url: "https://mock_bpp.com/images/item_1_1.jpg" - category_id: fresh-food - location_id: food-mall-location - price: - currency: INR - value: "200" - matched: true diff --git a/go.mod b/go.mod index 1a69f5f..b27c6ae 100644 --- a/go.mod +++ b/go.mod @@ -4,26 +4,25 @@ go 1.23.0 toolchain go1.23.7 -require golang.org/x/crypto v0.36.0 +require ( + github.com/stretchr/testify v1.10.0 + golang.org/x/crypto v0.36.0 +) require ( - github.com/google/go-cmp v0.7.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) -require ( - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect -) +require github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 require ( github.com/hashicorp/go-retryablehttp v0.7.7 diff --git a/go.sum b/go.sum index 128da49..adde50d 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,21 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -29,28 +24,22 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY= github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From be30b522b5f03f029f5f1dc4243fbcdde3f5b37e Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Thu, 27 Mar 2025 12:24:17 +0530 Subject: [PATCH 05/15] Update on the Review Comments 1. Resolved review comments. 2. Resolved Go linting issues. 3. Increase coverage from 93 to 96 percentage for module. --- cmd/adapter/main.go | 6 +-- cmd/adapter/main_test.go | 30 ++++-------- config/bap.yaml | 69 +++++++++++++++++++++++++++ config/bapCallerRouting-config.yaml | 3 ++ config/bpp-local.yaml | 63 ++++++++++++++++++++++++ config/bpp.yaml | 63 ++++++++++++++++++++++++ config/bppCallerRouting-config.yaml | 3 ++ config/bppRecieverRouting-config.yaml | 3 ++ config/byuerApp-config.yaml | 2 + config/sellerData.yaml | 69 +++++++++++++++++++++++++++ core/module/handler/config.go | 23 --------- core/module/handler/stdHandler.go | 29 ----------- core/module/handler/step.go | 9 ---- core/module/module.go | 21 ++------ 14 files changed, 291 insertions(+), 102 deletions(-) create mode 100644 config/bap.yaml create mode 100644 config/bapCallerRouting-config.yaml create mode 100644 config/bpp-local.yaml create mode 100644 config/bpp.yaml create mode 100644 config/bppCallerRouting-config.yaml create mode 100644 config/bppRecieverRouting-config.yaml create mode 100644 config/byuerApp-config.yaml create mode 100644 config/sellerData.yaml diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index 621b24b..a233ea5 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -25,10 +25,10 @@ type Config struct { Log log.Config `yaml:"log"` PluginManager *plugin.ManagerConfig `yaml:"pluginManager"` Modules []module.Config `yaml:"modules"` - HTTP httpConfig `yaml:"http"` // Nest http config + HTTP timeouts `yaml:"http"` // Nest http config } -type httpConfig struct { +type timeouts struct { Port string `yaml:"port"` Timeout timeoutConfig `yaml:"timeout"` } @@ -44,7 +44,7 @@ var runFunc = run func main() { // Define and parse command-line flags. - flag.StringVar(&configPath, "config", "../../config/clientSideHandler-config.yaml", "Path to the configuration file") + flag.StringVar(&configPath, "config", "../../config/onix/adapter.yaml", "Path to the configuration file") flag.Parse() // Use custom log for initial setup messages. diff --git a/cmd/adapter/main_test.go b/cmd/adapter/main_test.go index 3576ca3..14f87e5 100644 --- a/cmd/adapter/main_test.go +++ b/cmd/adapter/main_test.go @@ -73,22 +73,6 @@ func (m *MockPluginManager) SchemaValidator(ctx context.Context, cfg *plugin.Con return nil, nil } -// testConfigContent defines a mock configuration used in tests. -const testConfigContent = ` -appName: "TestApp" -http: - port: "8080" - timeout: - read: 5 - write: 5 - idle: 10 -` - -// initLogger is a mock function to initialize the logger. -var initLogger = func(cfg *Config) error { - return nil -} - // mockRun is a mock implementation of the `run` function, simulating a successful run. func mockRun(ctx context.Context, configPath string) error { return nil // Simulate a successful run @@ -110,7 +94,9 @@ func TestMainFunction(t *testing.T) { fs := flag.NewFlagSet("test", flag.ExitOnError) fs.StringVar(&configPath, "config", "../../config/clientSideHandler-config.yaml", "Path to the configuration file") - fs.Parse(os.Args[1:]) + if err := fs.Parse(os.Args[1:]); err != nil { + t.Fatalf("Failed to parse flags: %v", err) + } main() } @@ -378,7 +364,7 @@ func TestNewServerSuccess(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := &Config{ Modules: tt.modules, - HTTP: httpConfig{ + HTTP: timeouts{ Port: "8080", Timeout: timeoutConfig{ Read: 5, @@ -423,7 +409,7 @@ func TestNewServerFailure(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := &Config{ Modules: tt.modules, - HTTP: httpConfig{ + HTTP: timeouts{ Port: "8080", Timeout: timeoutConfig{ Read: 5, @@ -455,7 +441,7 @@ func TestValidateConfigSuccess(t *testing.T) { name: "Valid Config", cfg: Config{ AppName: "TestApp", - HTTP: httpConfig{ + HTTP: timeouts{ Port: "8080", }, }, @@ -483,7 +469,7 @@ func TestValidateConfigFailure(t *testing.T) { name: "Missing AppName", cfg: Config{ AppName: "", - HTTP: httpConfig{ + HTTP: timeouts{ Port: "8080", }, }, @@ -493,7 +479,7 @@ func TestValidateConfigFailure(t *testing.T) { name: "Missing Port", cfg: Config{ AppName: "TestApp", - HTTP: httpConfig{ + HTTP: timeouts{ Port: "", }, }, diff --git a/config/bap.yaml b/config/bap.yaml new file mode 100644 index 0000000..25c67b2 --- /dev/null +++ b/config/bap.yaml @@ -0,0 +1,69 @@ +appName: "bapAdapter" +log: + level: debug + destinations: + - type: stdout + context_keys: + - transaction_id + - message_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +plugin: + root: /app/plugins + pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip + plugins: + - publisher Src version raw comp zip + - nopschemavalidator + - router + - nopsigner + - nopsignvalidator + - reqpreprocessor + - gcpAuthMdw +modules: + - name: reciever + type: transaction + path: /reciever + targetType: msgQ + plugin: + schemaValidator: + id: nopschemavalidator + signValidator: + id: nopsignvalidator + publisher: + id: publisher + config: + project: ondc-seller-dev + topic: bapNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bapRecieverRouting-config.yaml + preProcessors: + - id: reqpreprocessor + steps: + steps: + - addRoute + signValidate + -addRout + customValidate + - name: transactionCaller + path: /caller + targetType: "http" + plugin: + signer: + id: nopsigner + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bapCallerRouting-config.yaml + preProcessors: + - id: reqpreprocessor + # postProcessors: + # - id: gcpAuthMdw + # config: + # audience: https://bpp-adapter-903496459467.asia-southeast1.run.app + # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bapCallerRouting-config.yaml b/config/bapCallerRouting-config.yaml new file mode 100644 index 0000000..a6702ea --- /dev/null +++ b/config/bapCallerRouting-config.yaml @@ -0,0 +1,3 @@ +routes: + - action: search + target: https://bpp-adapter-903496459467.asia-southeast1.run.app/reciever \ No newline at end of file diff --git a/config/bpp-local.yaml b/config/bpp-local.yaml new file mode 100644 index 0000000..5af87f4 --- /dev/null +++ b/config/bpp-local.yaml @@ -0,0 +1,63 @@ +appName: "bppClientService" +log: + level: debug + destinations: + - type: stdout + context_keys: + - transaction_id + - message_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +plugin: + root: extracted/plugins + pluginZipPath: plugins_bundle.zip + plugins: + - publisher + - nopschemavalidator + - router + - nopsigner + - nopsignvalidator + - reqpreprocessor + - gcpAuthMdw +module: + modules: + - name: transactionReciever + path: /reciever + targetType: msgQ + plugin: + schemaValidator: + id: nopschemavalidator + signValidator: + id: nopsignValidator + publisher: + id: publisher + config: + project: ondc-seller-dev + topic: clientSideTopic + Router: + id: router + config: + routingConfigPath: configs/bppRecieverRouting-config.yaml + preProcessors: + - id: reqpreprocessor + - name: transactionCaller + path: /caller + targetType: "http" + plugin: + signer: + id: nopsigner + Router: + id: router + config: + routingConfigPath: configs/bppCallerRouting-config.yaml + preProcessors: + - id: reqpreprocessor + postProcessors: + - id: gcpAuthMdw + config: + audience: "target" + diff --git a/config/bpp.yaml b/config/bpp.yaml new file mode 100644 index 0000000..a4d9a65 --- /dev/null +++ b/config/bpp.yaml @@ -0,0 +1,63 @@ +appName: "bppClientService" +log: + level: debug + destinations: + - type: stdout + context_keys: + - transaction_id + - message_id +http: + port: 8080 + timeout: + read: 30 + write: 30 + idle: 30 +plugin: + root: /app/plugins + pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip + plugins: + - publisher + - nopschemavalidator + - router + - nopsigner + - nopsignvalidator + - reqpreprocessor + - gcpAuthMdw +module: + modules: + - name: transactionReciever + path: /reciever + targetType: msgQ + plugin: + schemaValidator: + id: nopschemavalidator + signValidator: + id: nopsignvalidator + publisher: + id: publisher + config: + project: ondc-seller-dev + topic: bppNetworkReciever + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bppRecieverRouting-config.yaml + preProcessors: + - id: reqpreprocessor + - name: transactionCaller + path: /caller + targetType: "http" + plugin: + signer: + id: nopsigner + router: + id: router + config: + routingConfigPath: /mnt/gcs/configs/bppCallerRouting-config.yaml + preProcessors: + - id: reqpreprocessor + # postProcessors: + # - id: gcpAuthMdw + # config: + # audience: https://bap-adapter-903496459467.asia-southeast1.run.app + # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bppCallerRouting-config.yaml b/config/bppCallerRouting-config.yaml new file mode 100644 index 0000000..9aa5e39 --- /dev/null +++ b/config/bppCallerRouting-config.yaml @@ -0,0 +1,3 @@ +routes: + - action: on_search + target: targeturl \ No newline at end of file diff --git a/config/bppRecieverRouting-config.yaml b/config/bppRecieverRouting-config.yaml new file mode 100644 index 0000000..15fdb33 --- /dev/null +++ b/config/bppRecieverRouting-config.yaml @@ -0,0 +1,3 @@ +routes: + - action: search + target: https://sellerapp-903496459467.asia-southeast1.run.app \ No newline at end of file diff --git a/config/byuerApp-config.yaml b/config/byuerApp-config.yaml new file mode 100644 index 0000000..a441371 --- /dev/null +++ b/config/byuerApp-config.yaml @@ -0,0 +1,2 @@ +bap_url: "https://bap-csr-903496459467.asia-southeast1.run.app" # Replace with actual Beckn API endpoint +port: "8080" # The port on which the server will run \ No newline at end of file diff --git a/config/sellerData.yaml b/config/sellerData.yaml new file mode 100644 index 0000000..9a96fdf --- /dev/null +++ b/config/sellerData.yaml @@ -0,0 +1,69 @@ +- id: pooja-stores + descriptor: + name: Pooja Stores + locations: + - id: koramangala-4th-block-location + gps: "12.9349377,77.6055586" + categories: + - id: fresh_fruits + descriptor: + name: Fresh Fruits + - id: beverages + descriptor: + name: Beverages + items: + - id: item_2 + descriptor: + name: Green Apples Organic + images: + - url: "https://mock_bpp.com/images/item_2.jpg" + category_id: fresh_fruits + location_id: koramangala-4th-block-location + price: + currency: INR + value: "170" + matched: true + - id: item_1 + descriptor: + name: Red Apples + images: + - url: "https://mock_bpp.com/images/item_1.jpg" + category_id: fresh_fruits + location_id: koramangala-4th-block-location + price: + currency: INR + value: "90" + related: true + - id: item_7 + descriptor: + name: Green Apple Juice + images: + - url: "https://mock_bpp.com/images/item_7.jpg" + category_id: beverages + location_id: koramangala-4th-block-location + price: + currency: INR + value: "70" + matched: true +- id: food-mall + descriptor: + name: Food Mall + locations: + - id: food-mall-location + gps: "12.9349377,77.6055586" + categories: + - id: fresh-food + descriptor: + name: Fresh food + items: + - id: item_1_1 + descriptor: + name: Green Apple Salad + images: + - url: "https://mock_bpp.com/images/item_1_1.jpg" + category_id: fresh-food + location_id: food-mall-location + price: + currency: INR + value: "200" + matched: true diff --git a/core/module/handler/config.go b/core/module/handler/config.go index 08d90a3..209de7f 100644 --- a/core/module/handler/config.go +++ b/core/module/handler/config.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" - "github.com/beckn/beckn-onix/pkg/log" "github.com/beckn/beckn-onix/pkg/model" "github.com/beckn/beckn-onix/pkg/plugin" "github.com/beckn/beckn-onix/pkg/plugin/definition" @@ -25,24 +24,12 @@ type PluginManager interface { SchemaValidator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) } -// Provider represents a function that initializes an HTTP handler using a PluginManager. -type Provider func(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) - // Type defines different handler types for processing requests. type Type string const ( // HandlerTypeStd represents the standard handler type used for general request processing. HandlerTypeStd Type = "std" - - // HandlerTypeRegSub represents the registry subscriber handler type for handling registry subscription requests. - HandlerTypeRegSub Type = "regSub" - - // HandlerTypeNPSub represents the network participant subscriber handler type for handling network participant subscription requests. - HandlerTypeNPSub Type = "npSub" - - // HandlerTypeLookup represents the lookup handler type used for resolving service details. - HandlerTypeLookup Type = "lookUp" ) // PluginCfg holds the configuration for various plugins. @@ -108,13 +95,3 @@ func (s *Step) UnmarshalYAML(unmarshal func(interface{}) error) error { *s = step return nil } - -// DummyHandler is a basic HTTP handler that returns a fixed response. -func DummyHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Handler, error) { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - if _, err := w.Write([]byte("Dummy Handler Response")); err != nil { - log.Error(context.Background(), err, "failed to write nack response") - } - }), nil -} diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index e0f999b..08e9628 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -230,35 +230,6 @@ func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Conf steps[c.ID] = step } - // Register processing steps - for _, step := range cfg.Steps { - var s definition.Step - var err error - - switch step { - case "sign": - s, err = newSignStep(h.signer, h.km) - case "validateSign": - s, err = newValidateSignStep(h.signValidator, h.km) - case "validateSchema": - s, err = newValidateSchemaStep(h.schemaValidator) - case "addRoute": - s, err = newRouteStep(h.router) - case "broadcast": - s = &broadcastStep{} - default: - if customStep, exists := steps[step]; exists { - s = customStep - } else { - return fmt.Errorf("unrecognized step: %s", step) - } - } - - if err != nil { - return err - } - h.steps = append(h.steps, s) - } log.Infof(ctx, "Processor steps initialized: %v", cfg.Steps) return nil } diff --git a/core/module/handler/step.go b/core/module/handler/step.go index bcd185d..bbe01fd 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -155,12 +155,3 @@ func (s *addRouteStep) Run(ctx *model.StepContext) error { log.Debugf(ctx, "ctx.Route to %#v", ctx.Route) return nil } - -// broadcastStep is a stub for broadcasting. -type broadcastStep struct{} - -// Run is a placeholder for future implementation. -func (b *broadcastStep) Run(ctx *model.StepContext) error { - // TODO: Implement broadcast logic if needed - return nil -} diff --git a/core/module/module.go b/core/module/module.go index 9313712..3b0fcef 100644 --- a/core/module/module.go +++ b/core/module/module.go @@ -16,33 +16,22 @@ type Config struct { Handler handler.Config } +// Provider represents a function that initializes an HTTP handler using a PluginManager. +type Provider func(ctx context.Context, mgr handler.PluginManager, cfg *handler.Config) (http.Handler, error) + // handlerProviders maintains a mapping of handler types to their respective providers. -var handlerProviders = map[handler.Type]handler.Provider{ +var handlerProviders = map[handler.Type]Provider{ handler.HandlerTypeStd: handler.NewStdHandler, } -// GetDummyHandlerProviders returns a dummy handler provider mapping for testing purposes. -func GetDummyHandlerProviders() map[handler.Type]handler.Provider { - return map[handler.Type]handler.Provider{ - handler.HandlerTypeStd: handler.DummyHandler, - } -} - -// getHandlerProviders is a function to retrieve handler providers, which can be overridden for testing. -var getHandlerProviders = func() map[handler.Type]handler.Provider { - return handlerProviders -} - // Register initializes and registers handlers based on the provided configuration. // It iterates over the module configurations, retrieves appropriate handler providers, // and registers the handlers with the HTTP multiplexer. func Register(ctx context.Context, mCfgs []Config, mux *http.ServeMux, mgr handler.PluginManager) error { log.Debugf(ctx, "Registering modules with config: %#v", mCfgs) - - providers := getHandlerProviders() // Iterate over the handlers in the configuration. for _, c := range mCfgs { - rmp, ok := providers[c.Handler.Type] + rmp, ok := handlerProviders[c.Handler.Type] if !ok { return fmt.Errorf("invalid module : %s", c.Name) } From 7055c1c0d83545f8c75c0251c8a01d37bcaaf5c3 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Fri, 28 Mar 2025 22:45:58 +0530 Subject: [PATCH 06/15] updated code. 1. Resolved merge conflicts. 2. Resolved go linting issues. --- cmd/adapter/main.go | 12 ++-- cmd/adapter/main_test.go | 4 +- config/bap.yaml | 69 ------------------- config/bapCallerRouting-config.yaml | 3 - config/bpp-local.yaml | 63 ----------------- config/bpp.yaml | 63 ----------------- config/bppCallerRouting-config.yaml | 3 - config/bppRecieverRouting-config.yaml | 3 - config/byuerApp-config.yaml | 2 - config/sellerData.yaml | 69 ------------------- core/module/client/registery.go | 2 +- core/module/handler/config.go | 44 +----------- core/module/handler/stdHandler.go | 10 +-- core/module/handler/step.go | 27 +------- go.mod | 8 ++- go.sum | 13 ++++ pkg/log/log.go | 23 +++++++ pkg/model/model.go | 26 +++++-- .../schemaValidator/cmd/plugin.go | 2 +- .../schemaValidator/schemaValidator.go | 2 +- .../schemaValidator/schemaValidator_test.go | 2 +- pkg/plugin/manager.go | 11 ++- pkg/response/response.go | 30 +++++--- pkg/response/response_test.go | 2 +- 24 files changed, 117 insertions(+), 376 deletions(-) delete mode 100644 config/bap.yaml delete mode 100644 config/bapCallerRouting-config.yaml delete mode 100644 config/bpp-local.yaml delete mode 100644 config/bpp.yaml delete mode 100644 config/bppCallerRouting-config.yaml delete mode 100644 config/bppRecieverRouting-config.yaml delete mode 100644 config/byuerApp-config.yaml delete mode 100644 config/sellerData.yaml diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index a233ea5..a581a59 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -25,12 +25,12 @@ type Config struct { Log log.Config `yaml:"log"` PluginManager *plugin.ManagerConfig `yaml:"pluginManager"` Modules []module.Config `yaml:"modules"` - HTTP timeouts `yaml:"http"` // Nest http config + HTTP timeouts `yaml:"http"` } type timeouts struct { - Port string `yaml:"port"` - Timeout timeoutConfig `yaml:"timeout"` + Port string `yaml:"port"` + Timeouts timeoutConfig `yaml:"timeout"` } type timeoutConfig struct { @@ -138,9 +138,9 @@ func run(ctx context.Context, configPath string) error { httpServer := &http.Server{ Addr: net.JoinHostPort("", cfg.HTTP.Port), Handler: srv, - ReadTimeout: cfg.HTTP.Timeout.Read * time.Second, // Use timeouts from config - WriteTimeout: cfg.HTTP.Timeout.Write * time.Second, - IdleTimeout: cfg.HTTP.Timeout.Idle * time.Second, + ReadTimeout: cfg.HTTP.Timeouts.Read * time.Second, + WriteTimeout: cfg.HTTP.Timeouts.Write * time.Second, + IdleTimeout: cfg.HTTP.Timeouts.Idle * time.Second, } // Start HTTP server. diff --git a/cmd/adapter/main_test.go b/cmd/adapter/main_test.go index 14f87e5..db27567 100644 --- a/cmd/adapter/main_test.go +++ b/cmd/adapter/main_test.go @@ -366,7 +366,7 @@ func TestNewServerSuccess(t *testing.T) { Modules: tt.modules, HTTP: timeouts{ Port: "8080", - Timeout: timeoutConfig{ + Timeouts: timeoutConfig{ Read: 5, Write: 5, Idle: 10, @@ -411,7 +411,7 @@ func TestNewServerFailure(t *testing.T) { Modules: tt.modules, HTTP: timeouts{ Port: "8080", - Timeout: timeoutConfig{ + Timeouts: timeoutConfig{ Read: 5, Write: 5, Idle: 10, diff --git a/config/bap.yaml b/config/bap.yaml deleted file mode 100644 index 25c67b2..0000000 --- a/config/bap.yaml +++ /dev/null @@ -1,69 +0,0 @@ -appName: "bapAdapter" -log: - level: debug - destinations: - - type: stdout - context_keys: - - transaction_id - - message_id -http: - port: 8080 - timeout: - read: 30 - write: 30 - idle: 30 -plugin: - root: /app/plugins - pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip - plugins: - - publisher Src version raw comp zip - - nopschemavalidator - - router - - nopsigner - - nopsignvalidator - - reqpreprocessor - - gcpAuthMdw -modules: - - name: reciever - type: transaction - path: /reciever - targetType: msgQ - plugin: - schemaValidator: - id: nopschemavalidator - signValidator: - id: nopsignvalidator - publisher: - id: publisher - config: - project: ondc-seller-dev - topic: bapNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bapRecieverRouting-config.yaml - preProcessors: - - id: reqpreprocessor - steps: - steps: - - addRoute - signValidate - -addRout - customValidate - - name: transactionCaller - path: /caller - targetType: "http" - plugin: - signer: - id: nopsigner - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bapCallerRouting-config.yaml - preProcessors: - - id: reqpreprocessor - # postProcessors: - # - id: gcpAuthMdw - # config: - # audience: https://bpp-adapter-903496459467.asia-southeast1.run.app - # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bapCallerRouting-config.yaml b/config/bapCallerRouting-config.yaml deleted file mode 100644 index a6702ea..0000000 --- a/config/bapCallerRouting-config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -routes: - - action: search - target: https://bpp-adapter-903496459467.asia-southeast1.run.app/reciever \ No newline at end of file diff --git a/config/bpp-local.yaml b/config/bpp-local.yaml deleted file mode 100644 index 5af87f4..0000000 --- a/config/bpp-local.yaml +++ /dev/null @@ -1,63 +0,0 @@ -appName: "bppClientService" -log: - level: debug - destinations: - - type: stdout - context_keys: - - transaction_id - - message_id -http: - port: 8080 - timeout: - read: 30 - write: 30 - idle: 30 -plugin: - root: extracted/plugins - pluginZipPath: plugins_bundle.zip - plugins: - - publisher - - nopschemavalidator - - router - - nopsigner - - nopsignvalidator - - reqpreprocessor - - gcpAuthMdw -module: - modules: - - name: transactionReciever - path: /reciever - targetType: msgQ - plugin: - schemaValidator: - id: nopschemavalidator - signValidator: - id: nopsignValidator - publisher: - id: publisher - config: - project: ondc-seller-dev - topic: clientSideTopic - Router: - id: router - config: - routingConfigPath: configs/bppRecieverRouting-config.yaml - preProcessors: - - id: reqpreprocessor - - name: transactionCaller - path: /caller - targetType: "http" - plugin: - signer: - id: nopsigner - Router: - id: router - config: - routingConfigPath: configs/bppCallerRouting-config.yaml - preProcessors: - - id: reqpreprocessor - postProcessors: - - id: gcpAuthMdw - config: - audience: "target" - diff --git a/config/bpp.yaml b/config/bpp.yaml deleted file mode 100644 index a4d9a65..0000000 --- a/config/bpp.yaml +++ /dev/null @@ -1,63 +0,0 @@ -appName: "bppClientService" -log: - level: debug - destinations: - - type: stdout - context_keys: - - transaction_id - - message_id -http: - port: 8080 - timeout: - read: 30 - write: 30 - idle: 30 -plugin: - root: /app/plugins - pluginZipPath: /mnt/gcs/plugins/plugins_bundle.zip - plugins: - - publisher - - nopschemavalidator - - router - - nopsigner - - nopsignvalidator - - reqpreprocessor - - gcpAuthMdw -module: - modules: - - name: transactionReciever - path: /reciever - targetType: msgQ - plugin: - schemaValidator: - id: nopschemavalidator - signValidator: - id: nopsignvalidator - publisher: - id: publisher - config: - project: ondc-seller-dev - topic: bppNetworkReciever - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bppRecieverRouting-config.yaml - preProcessors: - - id: reqpreprocessor - - name: transactionCaller - path: /caller - targetType: "http" - plugin: - signer: - id: nopsigner - router: - id: router - config: - routingConfigPath: /mnt/gcs/configs/bppCallerRouting-config.yaml - preProcessors: - - id: reqpreprocessor - # postProcessors: - # - id: gcpAuthMdw - # config: - # audience: https://bap-adapter-903496459467.asia-southeast1.run.app - # serviceAccount: 903496459467-compute@developer.gserviceaccount.com diff --git a/config/bppCallerRouting-config.yaml b/config/bppCallerRouting-config.yaml deleted file mode 100644 index 9aa5e39..0000000 --- a/config/bppCallerRouting-config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -routes: - - action: on_search - target: targeturl \ No newline at end of file diff --git a/config/bppRecieverRouting-config.yaml b/config/bppRecieverRouting-config.yaml deleted file mode 100644 index 15fdb33..0000000 --- a/config/bppRecieverRouting-config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -routes: - - action: search - target: https://sellerapp-903496459467.asia-southeast1.run.app \ No newline at end of file diff --git a/config/byuerApp-config.yaml b/config/byuerApp-config.yaml deleted file mode 100644 index a441371..0000000 --- a/config/byuerApp-config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -bap_url: "https://bap-csr-903496459467.asia-southeast1.run.app" # Replace with actual Beckn API endpoint -port: "8080" # The port on which the server will run \ No newline at end of file diff --git a/config/sellerData.yaml b/config/sellerData.yaml deleted file mode 100644 index 9a96fdf..0000000 --- a/config/sellerData.yaml +++ /dev/null @@ -1,69 +0,0 @@ -- id: pooja-stores - descriptor: - name: Pooja Stores - locations: - - id: koramangala-4th-block-location - gps: "12.9349377,77.6055586" - categories: - - id: fresh_fruits - descriptor: - name: Fresh Fruits - - id: beverages - descriptor: - name: Beverages - items: - - id: item_2 - descriptor: - name: Green Apples Organic - images: - - url: "https://mock_bpp.com/images/item_2.jpg" - category_id: fresh_fruits - location_id: koramangala-4th-block-location - price: - currency: INR - value: "170" - matched: true - - id: item_1 - descriptor: - name: Red Apples - images: - - url: "https://mock_bpp.com/images/item_1.jpg" - category_id: fresh_fruits - location_id: koramangala-4th-block-location - price: - currency: INR - value: "90" - related: true - - id: item_7 - descriptor: - name: Green Apple Juice - images: - - url: "https://mock_bpp.com/images/item_7.jpg" - category_id: beverages - location_id: koramangala-4th-block-location - price: - currency: INR - value: "70" - matched: true -- id: food-mall - descriptor: - name: Food Mall - locations: - - id: food-mall-location - gps: "12.9349377,77.6055586" - categories: - - id: fresh-food - descriptor: - name: Fresh food - items: - - id: item_1_1 - descriptor: - name: Green Apple Salad - images: - - url: "https://mock_bpp.com/images/item_1_1.jpg" - category_id: fresh-food - location_id: food-mall-location - price: - currency: INR - value: "200" - matched: true diff --git a/core/module/client/registery.go b/core/module/client/registery.go index 2a688c6..57b8e71 100644 --- a/core/module/client/registery.go +++ b/core/module/client/registery.go @@ -24,7 +24,7 @@ type Config struct { // RegisteryClient encapsulates the logic for calling the subscribe and lookup endpoints. type RegisteryClient struct { Config *Config - Client *retryablehttp.Client // Retryable HTTP Client + Client *retryablehttp.Client } // NewRegisteryClient creates a new instance of Client. diff --git a/core/module/handler/config.go b/core/module/handler/config.go index 209de7f..fa2a966 100644 --- a/core/module/handler/config.go +++ b/core/module/handler/config.go @@ -2,7 +2,6 @@ package handler import ( "context" - "fmt" "net/http" "github.com/beckn/beckn-onix/pkg/model" @@ -10,7 +9,7 @@ import ( "github.com/beckn/beckn-onix/pkg/plugin/definition" ) -// PluginManager defines the methods required for managing plugins in stdHandler. +// PluginManager defines an interface for managing plugins dynamically. type PluginManager interface { Middleware(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error) @@ -53,45 +52,4 @@ type Config struct { RegistryURL string `yaml:"registryUrl"` Role model.Role SubscriberID string `yaml:"subscriberId"` - Trace map[string]bool -} - -// Step represents a named processing step. -type Step string - -const ( - // StepInitialize represents the initialization phase of the request processing pipeline. - StepInitialize Step = "initialize" - - // StepValidate represents the validation phase, where input data is checked for correctness. - StepValidate Step = "validate" - - // StepProcess represents the core processing phase of the request. - StepProcess Step = "process" - - // StepFinalize represents the finalization phase, where the response is prepared and sent. - StepFinalize Step = "finalize" -) - -// validSteps ensures only allowed step values are used. -var validSteps = map[Step]bool{ - StepInitialize: true, - StepValidate: true, - StepProcess: true, - StepFinalize: true, -} - -// UnmarshalYAML customizes YAML unmarshalling for Step to enforce valid values. -func (s *Step) UnmarshalYAML(unmarshal func(interface{}) error) error { - var stepName string - if err := unmarshal(&stepName); err != nil { - return err - } - - step := Step(stepName) - if !validSteps[step] { - return fmt.Errorf("invalid step: %s", stepName) - } - *s = step - return nil } diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index 08e9628..9ca3dbe 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -38,11 +38,11 @@ func NewStdHandler(ctx context.Context, mgr PluginManager, cfg *Config) (http.Ha SubscriberID: cfg.SubscriberID, role: cfg.Role, } - // Initialize plugins + // Initialize plugins. if err := h.initPlugins(ctx, mgr, &cfg.Plugins, cfg.RegistryURL); err != nil { return nil, fmt.Errorf("failed to initialize plugins: %w", err) } - // Initialize steps + // Initialize steps. if err := h.initSteps(ctx, mgr, cfg); err != nil { return nil, fmt.Errorf("failed to initialize steps: %w", err) } @@ -59,7 +59,7 @@ func (h *stdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } log.Request(r.Context(), r, ctx.Body) - // Execute processing steps + // Execute processing steps. for _, step := range h.steps { if err := step.Run(ctx); err != nil { log.Errorf(ctx, err, "%T.run(%v):%v", step, ctx, err) @@ -67,14 +67,14 @@ func (h *stdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } } - // Restore request body before forwarding or publishing + // Restore request body before forwarding or publishing. r.Body = io.NopCloser(bytes.NewReader(ctx.Body)) if ctx.Route == nil { response.SendAck(w) return } - // Handle routing based on the defined route type + // Handle routing based on the defined route type. route(ctx, r, w, h.publisher) } diff --git a/core/module/handler/step.go b/core/module/handler/step.go index bbe01fd..d4aa63e 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -1,7 +1,6 @@ package handler import ( - "context" "fmt" "strings" "time" @@ -74,17 +73,17 @@ func (s *validateSignStep) Run(ctx *model.StepContext) error { if len(headerValue) != 0 { if err := s.validate(ctx, headerValue); err != nil { ctx.RespHeader.Set(model.UnaAuthorizedHeaderGateway, unauthHeader) - return model.NewSignValidationErrf("failed to validate %s: %w", model.AuthHeaderGateway, err) + return model.NewSignValidationErr(fmt.Errorf("failed to validate %s: %w", model.AuthHeaderGateway, err)) } } headerValue = ctx.Request.Header.Get(model.AuthHeaderSubscriber) if len(headerValue) == 0 { ctx.RespHeader.Set(model.UnaAuthorizedHeaderSubscriber, unauthHeader) - return model.NewSignValidationErrf("%s missing", model.UnaAuthorizedHeaderSubscriber) + return model.NewSignValidationErr(fmt.Errorf("%s missing", model.UnaAuthorizedHeaderSubscriber)) } if err := s.validate(ctx, headerValue); err != nil { ctx.RespHeader.Set(model.UnaAuthorizedHeaderSubscriber, unauthHeader) - return model.NewSignValidationErrf("failed to validate %s: %w", model.AuthHeaderSubscriber, err) + return model.NewSignValidationErr(fmt.Errorf("failed to validate %s: %w", model.AuthHeaderSubscriber, err)) } return nil } @@ -113,15 +112,6 @@ type validateSchemaStep struct { validator definition.SchemaValidator } -// newValidateSchemaStep creates and returns the validateSchema step after validation -func newValidateSchemaStep(schemaValidator definition.SchemaValidator) (definition.Step, error) { - if schemaValidator == nil { - return nil, fmt.Errorf("invalid config: SchemaValidator plugin not configured") - } - log.Debug(context.Background(), "adding schema validator") - return &validateSchemaStep{validator: schemaValidator}, nil -} - // Run executes the schema validation step. func (s *validateSchemaStep) Run(ctx *model.StepContext) error { if err := s.validator.Validate(ctx, ctx.Request.URL, ctx.Body); err != nil { @@ -135,14 +125,6 @@ type addRouteStep struct { router definition.Router } -// newRouteStep initializes and returns a new routing step. -func newRouteStep(router definition.Router) (definition.Step, error) { - if router == nil { - return nil, fmt.Errorf("invalid config: Router plugin not configured") - } - return &addRouteStep{router: router}, nil -} - // Run executes the routing step. func (s *addRouteStep) Run(ctx *model.StepContext) error { route, err := s.router.Route(ctx, ctx.Request.URL, ctx.Body) @@ -150,8 +132,5 @@ func (s *addRouteStep) Run(ctx *model.StepContext) error { return fmt.Errorf("failed to determine route: %w", err) } log.Debugf(ctx, "Routing to %#v", route) - ctx.Route = route - - log.Debugf(ctx, "ctx.Route to %#v", ctx.Route) return nil } diff --git a/go.mod b/go.mod index 7ec431b..044bb06 100644 --- a/go.mod +++ b/go.mod @@ -27,9 +27,15 @@ require ( golang.org/x/text v0.23.0 // indirect ) -require golang.org/x/sys v0.31.0 // indirect +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.31.0 // indirect +) require ( github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/rs/zerolog v1.34.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 183730d..b00b8d5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -5,6 +6,7 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -22,14 +24,20 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= 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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -40,6 +48,9 @@ github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67F github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= @@ -47,6 +58,8 @@ golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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= diff --git a/pkg/log/log.go b/pkg/log/log.go index 8531eee..eabd9f0 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -25,11 +25,13 @@ type destination struct { Config map[string]string `yaml:"config"` } +// Destination types for logging output. const ( Stdout destinationType = "stdout" File destinationType = "file" ) +// Log levels define the severity of log messages. const ( DebugLevel level = "debug" InfoLevel level = "info" @@ -48,6 +50,7 @@ var logLevels = map[level]zerolog.Level{ PanicLevel: zerolog.PanicLevel, } +// Config represents the configuration for logging. type Config struct { Level level `yaml:"level"` Destinations []destination `yaml:"destinations"` @@ -60,6 +63,7 @@ var ( once sync.Once ) +// Logger instance and configuration. var ( ErrInvalidLogLevel = errors.New("invalid log level") ErrLogDestinationNil = errors.New("log Destinations cant be empty") @@ -163,6 +167,8 @@ func getLogger(config Config) (zerolog.Logger, error) { return newLogger, nil } +// InitLogger initializes the logger with the given configuration. +// It ensures that the logger is initialized only once using sync.Once. func InitLogger(c Config) error { var initErr error once.Do(func() { @@ -175,60 +181,74 @@ func InitLogger(c Config) error { return initErr } +// Debug logs a debug-level message with the provided context. func Debug(ctx context.Context, msg string) { logEvent(ctx, zerolog.DebugLevel, msg, nil) } +// Debugf logs a formatted debug-level message with the provided context. func Debugf(ctx context.Context, format string, v ...any) { msg := fmt.Sprintf(format, v...) logEvent(ctx, zerolog.DebugLevel, msg, nil) } +// Info logs an info-level message with the provided context. func Info(ctx context.Context, msg string) { logEvent(ctx, zerolog.InfoLevel, msg, nil) } +// Infof logs a formatted info-level message with the provided context. func Infof(ctx context.Context, format string, v ...any) { msg := fmt.Sprintf(format, v...) logEvent(ctx, zerolog.InfoLevel, msg, nil) } +// Warn logs a warning-level message with the provided context. func Warn(ctx context.Context, msg string) { logEvent(ctx, zerolog.WarnLevel, msg, nil) } +// Warnf logs a formatted warning-level message with the provided context. func Warnf(ctx context.Context, format string, v ...any) { msg := fmt.Sprintf(format, v...) logEvent(ctx, zerolog.WarnLevel, msg, nil) } +// Error logs an error-level message along with an error object. func Error(ctx context.Context, err error, msg string) { logEvent(ctx, zerolog.ErrorLevel, msg, err) } +// Errorf logs a formatted error-level message along with an error object. func Errorf(ctx context.Context, err error, format string, v ...any) { msg := fmt.Sprintf(format, v...) logEvent(ctx, zerolog.ErrorLevel, msg, err) } +// Fatal logs a fatal-level message along with an error object and exits the application. func Fatal(ctx context.Context, err error, msg string) { logEvent(ctx, zerolog.FatalLevel, msg, err) } +// Fatalf logs a formatted fatal-level message along with an error object and exits the application. func Fatalf(ctx context.Context, err error, format string, v ...any) { msg := fmt.Sprintf(format, v...) logEvent(ctx, zerolog.FatalLevel, msg, err) } +// Panic logs a panic-level message along with an error object and panics. func Panic(ctx context.Context, err error, msg string) { logEvent(ctx, zerolog.PanicLevel, msg, err) } +// Panicf logs a formatted panic-level message along with an error object and panics. func Panicf(ctx context.Context, err error, format string, v ...any) { msg := fmt.Sprintf(format, v...) logEvent(ctx, zerolog.PanicLevel, msg, err) } +// logEvent logs an event at the specified log level with an optional error message. +// It adds contextual information before logging the message. func logEvent(ctx context.Context, level zerolog.Level, msg string, err error) { event := logger.WithLevel(level) @@ -239,6 +259,7 @@ func logEvent(ctx context.Context, level zerolog.Level, msg string, err error) { event.Msg(msg) } +// Request logs details of an incoming HTTP request, including method, URL, body, and remote address. func Request(ctx context.Context, r *http.Request, body []byte) { event := logger.Info() addCtx(ctx, event) @@ -249,6 +270,7 @@ func Request(ctx context.Context, r *http.Request, body []byte) { Msg("HTTP Request") } +// addCtx adds context values to the log event based on configured context keys. func addCtx(ctx context.Context, event *zerolog.Event) { for _, key := range cfg.ContextKeys { val, ok := ctx.Value(key).(string) @@ -260,6 +282,7 @@ func addCtx(ctx context.Context, event *zerolog.Event) { } } +// Response logs details of an outgoing HTTP response, including method, URL, status code, and response time. func Response(ctx context.Context, r *http.Request, statusCode int, responseTime time.Duration) { event := logger.Info() addCtx(ctx, event) diff --git a/pkg/model/model.go b/pkg/model/model.go index c9876bf..b1cd0ae 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -40,14 +40,20 @@ const ( type contextKey string +// MsgIDKey is the context key used to store and retrieve the message ID in a request context. const MsgIDKey = contextKey("message_id") +// Role defines the type of participant in the network. type Role string const ( - RoleBAP Role = "bap" - RoleBPP Role = "bpp" - RoleGateway Role = "gateway" + // RoleBAP represents a Buyer App Participant (BAP) in the network. + RoleBAP Role = "bap" + // RoleBPP represents a Buyer Platform Participant (BPP) in the network. + RoleBPP Role = "bpp" + // RoleGateway represents a Gateway that facilitates communication in the network. + RoleGateway Role = "gateway" + // RoleRegistery represents the Registry that maintains network participant details. RoleRegistery Role = "registery" ) @@ -91,22 +97,32 @@ type StepContext struct { RespHeader http.Header } +// WithContext updates the existing StepContext with a new context. func (ctx *StepContext) WithContext(newCtx context.Context) { ctx.Context = newCtx } +// Status represents the acknowledgment status in a response. type Status string const ( - StatusACK Status = "ACK" + // StatusACK indicates a successful acknowledgment. + StatusACK Status = "ACK" + // StatusNACK indicates a negative acknowledgment or failure. StatusNACK Status = "NACK" ) +// Ack represents an acknowledgment response. type Ack struct { + // Status holds the acknowledgment status (ACK/NACK). Status Status `json:"status"` } + +// Message represents the structure of a response message. type Message struct { - Ack Ack `json:"ack"` + // Ack contains the acknowledgment status. + Ack Ack `json:"ack"` + // Error holds error details, if any, in the response. Error *Error `json:"error,omitempty"` } diff --git a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go index 2a8f44a..b45f17e 100644 --- a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go +++ b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go @@ -5,7 +5,7 @@ import ( "errors" definition "github.com/beckn/beckn-onix/pkg/plugin/definition" - schemaValidator "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemaValidator" + schemaValidator "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemavalidator" ) // schemaValidatorProvider provides instances of schemaValidator. diff --git a/pkg/plugin/implementation/schemaValidator/schemaValidator.go b/pkg/plugin/implementation/schemaValidator/schemaValidator.go index 2d6b189..a981778 100644 --- a/pkg/plugin/implementation/schemaValidator/schemaValidator.go +++ b/pkg/plugin/implementation/schemaValidator/schemaValidator.go @@ -1,4 +1,4 @@ -package schemaValidator +package schemavalidator import ( "context" diff --git a/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go b/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go index 277b539..7264e2e 100644 --- a/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go +++ b/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go @@ -1,4 +1,4 @@ -package schemaValidator +package schemavalidator import ( "context" diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 44f6bcf..2bf8c20 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -149,8 +149,15 @@ func (m *Manager) Router(ctx context.Context, cfg *Config) (definition.Router, e if err != nil { return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) } - return rp.New(ctx, cfg.Config) - + router, closer, err := rp.New(ctx, cfg.Config) + if closer != nil { + m.addCloser(func() { + if err := closer(); err != nil { + panic(err) + } + }) + } + return router, nil } func (m *Manager) Middleware(ctx context.Context, cfg *Config) (func(http.Handler) http.Handler, error) { diff --git a/pkg/response/response.go b/pkg/response/response.go index 976c1e0..c72b475 100644 --- a/pkg/response/response.go +++ b/pkg/response/response.go @@ -12,10 +12,16 @@ import ( "github.com/beckn/beckn-onix/pkg/model" ) +// Error represents a standardized error response used across the system. type Error struct { - Code string `json:"code,omitempty"` + // Code is a short, machine-readable error code. + Code string `json:"code,omitempty"` + + // Message provides a human-readable description of the error. Message string `json:"message,omitempty"` - Paths string `json:"paths,omitempty"` + + // Paths indicates the specific field(s) or endpoint(s) related to the error. + Paths string `json:"paths,omitempty"` } // SchemaValidationErr represents a collection of schema validation failures. @@ -32,13 +38,18 @@ func (e *SchemaValidationErr) Error() string { return strings.Join(errorMessages, "; ") } +// Message represents a standard message structure with acknowledgment and error information. type Message struct { + // Ack contains the acknowledgment status of the response. Ack struct { Status string `json:"status,omitempty"` } `json:"ack,omitempty"` + + // Error holds error details if any occurred during processing. Error *Error `json:"error,omitempty"` } +// SendAck sends an acknowledgment response (ACK) to the client. func SendAck(w http.ResponseWriter) { resp := &model.Response{ Message: model.Message{ @@ -59,7 +70,8 @@ func SendAck(w http.ResponseWriter) { } } -func nack(w http.ResponseWriter, err *model.Error, status int, ctx context.Context) { +// nack sends a negative acknowledgment (NACK) response with an error message. +func nack(ctx context.Context, w http.ResponseWriter, err *model.Error, status int) { resp := &model.Response{ Message: model.Message{ Ack: model.Ack{ @@ -80,6 +92,7 @@ func nack(w http.ResponseWriter, err *model.Error, status int, ctx context.Conte } } +// internalServerError generates an internal server error response. func internalServerError(ctx context.Context) *model.Error { return &model.Error{ Code: http.StatusText(http.StatusInternalServerError), @@ -87,6 +100,7 @@ func internalServerError(ctx context.Context) *model.Error { } } +// SendNack processes different types of errors and sends an appropriate NACK response. func SendNack(ctx context.Context, w http.ResponseWriter, err error) { var schemaErr *model.SchemaValidationErr var signErr *model.SignValidationErr @@ -95,19 +109,19 @@ func SendNack(ctx context.Context, w http.ResponseWriter, err error) { switch { case errors.As(err, &schemaErr): - nack(w, schemaErr.BecknError(), http.StatusBadRequest, ctx) + nack(ctx, w, schemaErr.BecknError(), http.StatusBadRequest) return case errors.As(err, &signErr): - nack(w, signErr.BecknError(), http.StatusUnauthorized, ctx) + nack(ctx, w, signErr.BecknError(), http.StatusUnauthorized) return case errors.As(err, &badReqErr): - nack(w, badReqErr.BecknError(), http.StatusBadRequest, ctx) + nack(ctx, w, badReqErr.BecknError(), http.StatusBadRequest) return case errors.As(err, ¬FoundErr): - nack(w, notFoundErr.BecknError(), http.StatusNotFound, ctx) + nack(ctx, w, notFoundErr.BecknError(), http.StatusNotFound) return default: - nack(w, internalServerError(ctx), http.StatusInternalServerError, ctx) + nack(ctx, w, internalServerError(ctx), http.StatusInternalServerError) return } } diff --git a/pkg/response/response_test.go b/pkg/response/response_test.go index 7e62aca..8b7f748 100644 --- a/pkg/response/response_test.go +++ b/pkg/response/response_test.go @@ -234,7 +234,7 @@ func TestNack_1(t *testing.T) { return } - nack(w, tt.err, tt.status, ctx) + nack(ctx, w, tt.err, tt.status) if !tt.useBadWrite { recorder, ok := w.(*httptest.ResponseRecorder) if !ok { From 98c04b1f9f6284a38ee2dc1b837f62be139e7db0 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Fri, 28 Mar 2025 22:50:27 +0530 Subject: [PATCH 07/15] Resolved linting issue --- pkg/plugin/implementation/schemaValidator/schemaValidator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/plugin/implementation/schemaValidator/schemaValidator.go b/pkg/plugin/implementation/schemaValidator/schemaValidator.go index a981778..85f2bf7 100644 --- a/pkg/plugin/implementation/schemaValidator/schemaValidator.go +++ b/pkg/plugin/implementation/schemaValidator/schemaValidator.go @@ -61,14 +61,14 @@ func (v *SchemaValidator) Validate(ctx context.Context, url *url.URL, data []byt } // Extract domain, version, and endpoint from the payload and uri. - cxt_domain := payloadData.Context.Domain + cxtDomain := 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.ToLower(cxtDomain) domain = strings.ReplaceAll(domain, ":", "_") // Construct the schema file name. From 9bfb65079e42133b99bf50a4f614ebe4ebc3e762 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Sat, 29 Mar 2025 16:08:28 +0530 Subject: [PATCH 08/15] Update on review comments 1. changed implementations from camelcase to lowercase 2. added removed initSteps. 3. updated rout struct for model.go --- cmd/adapter/main_test.go | 3 +- core/module/handler/stdHandler.go | 35 +++++++++++++++++-- core/module/handler/step.go | 33 +++++++++++++++++ pkg/model/model.go | 6 ++-- .../requestPreProcessor/cmd/plugin.go | 2 +- .../schemaValidator/cmd/plugin.go | 6 ++-- .../implementation/signVerifier/cmd/plugin.go | 5 ++- .../signVerifier/signVerifier.go | 2 +- .../signVerifier/signVerifier_test.go | 2 +- 9 files changed, 77 insertions(+), 17 deletions(-) diff --git a/cmd/adapter/main_test.go b/cmd/adapter/main_test.go index db27567..1a3182d 100644 --- a/cmd/adapter/main_test.go +++ b/cmd/adapter/main_test.go @@ -24,8 +24,7 @@ type MockPluginManager struct { // Middleware returns a middleware function based on the provided configuration. func (m *MockPluginManager) Middleware(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) { - args := m.Called(ctx, cfg) - return args.Get(0).(func(http.Handler) http.Handler), args.Error(1) + return nil, nil } // SignValidator returns a mock implementation of the Verifier interface. diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index 9ca3dbe..32222cd 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -111,7 +111,7 @@ func (h *stdHandler) subID(ctx context.Context) string { // route handles request forwarding or message publishing based on the routing type. func route(ctx *model.StepContext, r *http.Request, w http.ResponseWriter, pb definition.Publisher) { log.Debugf(ctx, "Routing to ctx.Route to %#v", ctx.Route) - switch ctx.Route.Type { + switch ctx.Route.TargetType { case "url": log.Infof(ctx.Context, "Forwarding request to URL: %s", ctx.Route.URL) proxy(r, w, ctx.Route.URL) @@ -123,7 +123,7 @@ func route(ctx *model.StepContext, r *http.Request, w http.ResponseWriter, pb de response.SendNack(ctx, w, err) return } - log.Infof(ctx.Context, "Publishing message to: %s", ctx.Route.Publisher) + log.Infof(ctx.Context, "Publishing message to: %s", ctx.Route.PublisherID) if err := pb.Publish(ctx, ctx.Body); err != nil { log.Errorf(ctx.Context, err, "Failed to publish message") http.Error(w, "Error publishing message", http.StatusInternalServerError) @@ -131,7 +131,7 @@ func route(ctx *model.StepContext, r *http.Request, w http.ResponseWriter, pb de return } default: - err := fmt.Errorf("unknown route type: %s", ctx.Route.Type) + err := fmt.Errorf("unknown route type: %s", ctx.Route.TargetType) log.Errorf(ctx.Context, err, "Invalid configuration:%v", err) response.SendNack(ctx, w, err) return @@ -230,6 +230,35 @@ func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Conf steps[c.ID] = step } + // Register processing steps + for _, step := range cfg.Steps { + var s definition.Step + var err error + + switch step { + case "sign": + s, err = newSignStep(h.signer, h.km) + case "validateSign": + s, err = newValidateSignStep(h.signValidator, h.km) + case "validateSchema": + s, err = newValidateSchemaStep(h.schemaValidator) + case "addRoute": + s, err = newRouteStep(h.router) + case "broadcast": + s = &broadcastStep{} + default: + if customStep, exists := steps[step]; exists { + s = customStep + } else { + return fmt.Errorf("unrecognized step: %s", step) + } + } + + if err != nil { + return err + } + h.steps = append(h.steps, s) + } log.Infof(ctx, "Processor steps initialized: %v", cfg.Steps) return nil } diff --git a/core/module/handler/step.go b/core/module/handler/step.go index d4aa63e..f7f8abc 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -1,6 +1,7 @@ package handler import ( + "context" "fmt" "strings" "time" @@ -112,6 +113,15 @@ type validateSchemaStep struct { validator definition.SchemaValidator } +// newValidateSchemaStep creates and returns the validateSchema step after validation. +func newValidateSchemaStep(schemaValidator definition.SchemaValidator) (definition.Step, error) { + if schemaValidator == nil { + return nil, fmt.Errorf("invalid config: SchemaValidator plugin not configured") + } + log.Debug(context.Background(), "adding schema validator") + return &validateSchemaStep{validator: schemaValidator}, nil +} + // Run executes the schema validation step. func (s *validateSchemaStep) Run(ctx *model.StepContext) error { if err := s.validator.Validate(ctx, ctx.Request.URL, ctx.Body); err != nil { @@ -125,6 +135,14 @@ type addRouteStep struct { router definition.Router } +// newRouteStep creates and returns the addRoute step after validation. +func newRouteStep(router definition.Router) (definition.Step, error) { + if router == nil { + return nil, fmt.Errorf("invalid config: Router plugin not configured") + } + return &addRouteStep{router: router}, nil +} + // Run executes the routing step. func (s *addRouteStep) Run(ctx *model.StepContext) error { route, err := s.router.Route(ctx, ctx.Request.URL, ctx.Body) @@ -132,5 +150,20 @@ func (s *addRouteStep) Run(ctx *model.StepContext) error { return fmt.Errorf("failed to determine route: %w", err) } log.Debugf(ctx, "Routing to %#v", route) + ctx.Route = &model.Route{ + TargetType: route.TargetType, + PublisherID: route.PublisherID, + URL: route.URL, + } + log.Debugf(ctx, "ctx.Route to %#v", ctx.Route) + return nil +} + +// broadcastStep is a stub implementation of a step that handles broadcasting messages. +type broadcastStep struct{} + +// Run executes the broadcast step. +func (b *broadcastStep) Run(ctx *model.StepContext) error { + // TODO: Implement broadcast logic if needed return nil } diff --git a/pkg/model/model.go b/pkg/model/model.go index b1cd0ae..2038d94 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -81,9 +81,9 @@ func (r *Role) UnmarshalYAML(unmarshal func(interface{}) error) error { // Route represents a network route for message processing. type Route struct { - Type string - URL *url.URL - Publisher string + TargetType string // "url" or "msgq" or "bap" or "bpp" + PublisherID string // For message queues + URL *url.URL // For API calls } // StepContext holds context information for a request processing step. diff --git a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go index 4a05ecc..4d8d3a8 100644 --- a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go +++ b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - requestpreprocessor "github.com/beckn/beckn-onix/pkg/plugin/implementation/requestPreProcessor" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/requestpreprocessor" ) type provider struct{} diff --git a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go index b45f17e..fb257c9 100644 --- a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go +++ b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go @@ -4,8 +4,8 @@ import ( "context" "errors" - definition "github.com/beckn/beckn-onix/pkg/plugin/definition" - schemaValidator "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemavalidator" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemavalidator" ) // schemaValidatorProvider provides instances of schemaValidator. @@ -24,7 +24,7 @@ func (vp schemaValidatorProvider) New(ctx context.Context, config map[string]str } // Create a new schemaValidator instance with the provided configuration - return schemaValidator.New(ctx, &schemaValidator.Config{ + return schemavalidator.New(ctx, &schemavalidator.Config{ SchemaDir: schemaDir, }) } diff --git a/pkg/plugin/implementation/signVerifier/cmd/plugin.go b/pkg/plugin/implementation/signVerifier/cmd/plugin.go index 35c1287..d260057 100644 --- a/pkg/plugin/implementation/signVerifier/cmd/plugin.go +++ b/pkg/plugin/implementation/signVerifier/cmd/plugin.go @@ -5,8 +5,7 @@ import ( "errors" "github.com/beckn/beckn-onix/pkg/plugin/definition" - - verifier "github.com/beckn/beckn-onix/pkg/plugin/implementation/signVerifier" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/signverifier" ) // VerifierProvider provides instances of Verifier. @@ -18,7 +17,7 @@ func (vp VerifierProvider) New(ctx context.Context, config map[string]string) (d return nil, nil, errors.New("context cannot be nil") } - return verifier.New(ctx, &verifier.Config{}) + return signverifier.New(ctx, &signverifier.Config{}) } // Provider is the exported symbol that the plugin manager will look for. diff --git a/pkg/plugin/implementation/signVerifier/signVerifier.go b/pkg/plugin/implementation/signVerifier/signVerifier.go index 963d137..b76dd6d 100644 --- a/pkg/plugin/implementation/signVerifier/signVerifier.go +++ b/pkg/plugin/implementation/signVerifier/signVerifier.go @@ -1,4 +1,4 @@ -package verifier +package signverifier import ( "context" diff --git a/pkg/plugin/implementation/signVerifier/signVerifier_test.go b/pkg/plugin/implementation/signVerifier/signVerifier_test.go index 36da03a..0e22eb8 100644 --- a/pkg/plugin/implementation/signVerifier/signVerifier_test.go +++ b/pkg/plugin/implementation/signVerifier/signVerifier_test.go @@ -1,4 +1,4 @@ -package verifier +package signverifier import ( "context" From f8253c184c96bcb86ef1845e722f40b2caa409ce Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Sat, 29 Mar 2025 18:36:35 +0530 Subject: [PATCH 09/15] add test case for registry client --- core/module/client/registry_test.go | 261 ++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 core/module/client/registry_test.go diff --git a/core/module/client/registry_test.go b/core/module/client/registry_test.go new file mode 100644 index 0000000..f8e7eaf --- /dev/null +++ b/core/module/client/registry_test.go @@ -0,0 +1,261 @@ +package client + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/beckn/beckn-onix/pkg/model" + "github.com/stretchr/testify/require" +) + +// MockRegistryClient is a mock implementation of the RegistryClient. +type MockRegistryClient struct { + SubscribeFunc func(ctx context.Context, subscription *model.Subscription) error +} + +// Subscribe calls the mock Subscribe function. +func (m *MockRegistryClient) Subscribe(ctx context.Context, subscription *model.Subscription) error { + return m.SubscribeFunc(ctx, subscription) +} + +// TestSubscribeSuccess verifies that the Subscribe function succeeds when the server responds with HTTP 200. +func TestSubscribeSuccess(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("{}")) + })) + defer server.Close() + + client := NewRegisteryClient(&Config{ + RegisteryURL: server.URL, + RetryMax: 3, + RetryWaitMin: time.Millisecond * 100, + RetryWaitMax: time.Millisecond * 500, + }) + + subscription := &model.Subscription{ + KeyID: "test-key", + SigningPublicKey: "test-signing-key", + EncrPublicKey: "test-encryption-key", + ValidFrom: time.Now(), + ValidUntil: time.Now().Add(24 * time.Hour), + Status: "SUBSCRIBED", + } + + err := client.Subscribe(context.Background(), subscription) + require.NoError(t, err) +} + +// TestSubscribeFailureWithMock tests different failure scenarios using a mock client. +func TestSubscribeFailureWithMock(t *testing.T) { + tests := []struct { + name string + mockError error + expectError bool + }{ + { + name: "Failed subscription - Internal Server Error", + mockError: errors.New("internal server error"), + expectError: true, + }, + { + name: "Failed subscription - Bad Request", + mockError: errors.New("bad request"), + expectError: true, + }, + { + name: "Request Timeout", + mockError: context.DeadlineExceeded, + expectError: true, + }, + { + name: "Network Failure", + mockError: errors.New("network failure"), + expectError: true, + }, + { + name: "JSON Marshalling Failure", + mockError: errors.New("json marshalling failure"), + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := &MockRegistryClient{ + SubscribeFunc: func(ctx context.Context, subscription *model.Subscription) error { + return tt.mockError + }, + } + + subscription := &model.Subscription{ + KeyID: "test-key", + SigningPublicKey: "test-signing-key", + EncrPublicKey: "test-encryption-key", + ValidFrom: time.Now(), + ValidUntil: time.Now().Add(24 * time.Hour), + Status: "SUBSCRIBED", + } + + if tt.name == "JSON Marshalling Failure" { + invalidSubscription := &model.Subscription{} + invalidSubscription.ValidFrom = time.Unix(0, 0) // Invalid zero timestamp + subscription = invalidSubscription + } + + err := mockClient.Subscribe(context.Background(), subscription) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +// TestLookupSuccess tests successful lookup scenarios. +func TestLookupSuccess(t *testing.T) { + tests := []struct { + name string + responseBody interface{} + responseCode int + }{ + { + name: "Successful lookup", + responseBody: []model.Subscription{ + { + Subscriber: model.Subscriber{ + SubscriberID: "123", + }, + KeyID: "test-key", + SigningPublicKey: "test-signing-key", + EncrPublicKey: "test-encryption-key", + ValidFrom: time.Now(), + ValidUntil: time.Now().Add(24 * time.Hour), + Status: "SUBSCRIBED", + }, + }, + responseCode: http.StatusOK, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(tc.responseCode) + if tc.responseBody != nil { + bodyBytes, _ := json.Marshal(tc.responseBody) + w.Write(bodyBytes) + } + })) + defer server.Close() + + config := &Config{ + RegisteryURL: server.URL, + RetryMax: 1, + RetryWaitMin: 1 * time.Millisecond, + RetryWaitMax: 2 * time.Millisecond, + } + rClient := NewRegisteryClient(config) + ctx := context.Background() + subscription := &model.Subscription{ + Subscriber: model.Subscriber{ + SubscriberID: "123", + }, + KeyID: "test-key", + SigningPublicKey: "test-signing-key", + EncrPublicKey: "test-encryption-key", + ValidFrom: time.Now(), + ValidUntil: time.Now().Add(24 * time.Hour), + Status: "SUBSCRIBED", + } + + result, err := rClient.Lookup(ctx, subscription) + require.NoError(t, err) + require.NotEmpty(t, result) + require.Equal(t, subscription.Subscriber.SubscriberID, result[0].Subscriber.SubscriberID) + }) + } +} + +// TestLookupFailure tests failure scenarios for the Lookup function. +func TestLookupFailure(t *testing.T) { + tests := []struct { + name string + responseBody interface{} + responseCode int + setupMock func(*httptest.Server) + }{ + { + name: "Lookup failure - non 200 status", + responseBody: "Internal Server Error", + responseCode: http.StatusInternalServerError, + }, + { + name: "Invalid JSON response", + responseBody: "Invalid JSON", + responseCode: http.StatusOK, + }, + { + name: "Server timeout", + setupMock: func(server *httptest.Server) { + server.Config.WriteTimeout = 1 * time.Millisecond // Force timeout + }, + }, + { + name: "Empty response body", + responseBody: "", + responseCode: http.StatusOK, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if tc.responseCode != 0 { // Prevent WriteHeader(0) error + w.WriteHeader(tc.responseCode) + } + if tc.responseBody != nil { + if str, ok := tc.responseBody.(string); ok { + w.Write([]byte(str)) + } else { + bodyBytes, _ := json.Marshal(tc.responseBody) + w.Write(bodyBytes) + } + } + })) + defer server.Close() + + if tc.setupMock != nil { + tc.setupMock(server) + } + + config := &Config{ + RegisteryURL: server.URL, + RetryMax: 0, // Prevent excessive retries + RetryWaitMin: 1 * time.Millisecond, + RetryWaitMax: 2 * time.Millisecond, + } + rClient := NewRegisteryClient(config) + ctx := context.Background() + subscription := &model.Subscription{ + Subscriber: model.Subscriber{}, + KeyID: "test-key", + SigningPublicKey: "test-signing-key", + EncrPublicKey: "test-encryption-key", + ValidFrom: time.Now(), + ValidUntil: time.Now().Add(24 * time.Hour), + Status: "SUBSCRIBED", + } + + result, err := rClient.Lookup(ctx, subscription) + require.Error(t, err) + require.Empty(t, result) + }) + } +} From 244a7be7c1c7dbd9f2356fcac2fdd1d5ea3c8a3a Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Sun, 30 Mar 2025 16:45:50 +0530 Subject: [PATCH 10/15] temporary removed camelcase files for renaming --- pkg/plugin/definition/signVerifier.go | 22 -- .../requestPreProcessor/cmd/plugin.go | 21 -- .../requestPreProcessor/cmd/plugin_test.go | 85 ----- .../requestPreProcessor/reqpreprocessor.go | 105 ------ .../reqpreprocessor_test.go | 178 --------- pkg/plugin/implementation/router/router.go | 36 +- .../schemaValidator/cmd/plugin.go | 33 -- .../schemaValidator/cmd/plugin_test.go | 150 -------- .../schemaValidator/schemaValidator.go | 197 ---------- .../schemaValidator/schemaValidator_test.go | 353 ------------------ .../implementation/signVerifier/cmd/plugin.go | 24 -- .../signVerifier/cmd/plugin_test.go | 89 ----- .../signVerifier/signVerifier.go | 120 ------ .../signVerifier/signVerifier_test.go | 153 -------- 14 files changed, 24 insertions(+), 1542 deletions(-) delete mode 100644 pkg/plugin/definition/signVerifier.go delete mode 100644 pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go delete mode 100644 pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go delete mode 100644 pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go delete mode 100644 pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go delete mode 100644 pkg/plugin/implementation/schemaValidator/cmd/plugin.go delete mode 100644 pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go delete mode 100644 pkg/plugin/implementation/schemaValidator/schemaValidator.go delete mode 100644 pkg/plugin/implementation/schemaValidator/schemaValidator_test.go delete mode 100644 pkg/plugin/implementation/signVerifier/cmd/plugin.go delete mode 100644 pkg/plugin/implementation/signVerifier/cmd/plugin_test.go delete mode 100644 pkg/plugin/implementation/signVerifier/signVerifier.go delete mode 100644 pkg/plugin/implementation/signVerifier/signVerifier_test.go diff --git a/pkg/plugin/definition/signVerifier.go b/pkg/plugin/definition/signVerifier.go deleted file mode 100644 index fe36358..0000000 --- a/pkg/plugin/definition/signVerifier.go +++ /dev/null @@ -1,22 +0,0 @@ -package definition - -import "context" - -// Verifier defines the method for verifying signatures. -type Verifier interface { - // Verify checks the validity of the signature for the given body. - Verify(ctx context.Context, body []byte, header []byte, publicKeyBase64 string) (bool, error) - Close() error // Close for releasing resources -} - -// VerifierProvider initializes a new Verifier instance with the given config. -type VerifierProvider interface { - // New creates a new Verifier instance based on the provided config. - New(ctx context.Context, config map[string]string) (Verifier, func() error, error) -} - -// PublicKeyManager is the interface for key management plugin. -type PublicKeyManager interface { - // PublicKey retrieves the public key for the given subscriberID and keyID. - PublicKey(ctx context.Context, subscriberID string, keyID string) (string, error) -} diff --git a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go deleted file mode 100644 index 4d8d3a8..0000000 --- a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "context" - "net/http" - "strings" - - "github.com/beckn/beckn-onix/pkg/plugin/implementation/requestpreprocessor" -) - -type provider struct{} - -func (p provider) New(ctx context.Context, c map[string]string) (func(http.Handler) http.Handler, error) { - config := &requestpreprocessor.Config{} - if contextKeysStr, ok := c["ContextKeys"]; ok { - config.ContextKeys = strings.Split(contextKeysStr, ",") - } - return requestpreprocessor.NewUUIDSetter(config) -} - -var Provider = provider{} diff --git a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go deleted file mode 100644 index 0890dbc..0000000 --- a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "context" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TODO: Will Split this into success and fail (two test cases) -func TestProviderNew(t *testing.T) { - testCases := []struct { - name string - config map[string]string - expectedError bool - expectedStatus int - prepareRequest func(req *http.Request) - }{ - { - name: "No Config", - config: map[string]string{}, - expectedError: true, - expectedStatus: http.StatusOK, - prepareRequest: func(req *http.Request) { - // Add minimal required headers. - req.Header.Set("context", "test-context") - req.Header.Set("transaction_id", "test-transaction") - }, - }, - { - name: "With Check Keys", - config: map[string]string{ - "ContextKeys": "message_id,transaction_id", - }, - expectedError: false, - expectedStatus: http.StatusOK, - prepareRequest: func(req *http.Request) { - // Add headers matching the check keys. - req.Header.Set("context", "test-context") - req.Header.Set("transaction_id", "test-transaction") - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - requestBody := `{ - "context": { - "transaction_id": "abc" - } - }` - - p := provider{} - middleware, err := p.New(context.Background(), tc.config) - if tc.expectedError { - assert.Error(t, err) - return - } - require.NoError(t, err) - assert.NotNil(t, middleware) - - testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - req := httptest.NewRequest("POST", "/", strings.NewReader(requestBody)) - req.Header.Set("Content-Type", "application/json") - if tc.prepareRequest != nil { - tc.prepareRequest(req) - } - - w := httptest.NewRecorder() - middlewaredHandler := middleware(testHandler) - middlewaredHandler.ServeHTTP(w, req) - assert.Equal(t, tc.expectedStatus, w.Code, "Unexpected response status") - responseBody := w.Body.String() - t.Logf("Response Body: %s", responseBody) - - }) - } -} diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go deleted file mode 100644 index 13d4da0..0000000 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ /dev/null @@ -1,105 +0,0 @@ -package requestpreprocessor - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - - "github.com/google/uuid" -) - -type Config struct { - ContextKeys []string - Role string -} - -type becknRequest struct { - Context map[string]any `json:"context"` -} - -type contextKeyType string - -const contextKey = "context" -const subscriberIDKey contextKeyType = "subscriber_id" - -func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { - if err := validateConfig(cfg); err != nil { - return nil, err - } - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, _ := io.ReadAll(r.Body) - var req becknRequest - if err := json.Unmarshal(body, &req); err != nil { - http.Error(w, "Failed to decode request body", http.StatusBadRequest) - return - } - if req.Context == nil { - http.Error(w, fmt.Sprintf("%s field not found.", contextKey), http.StatusBadRequest) - return - } - var subID any - switch cfg.Role { - case "bap": - subID = req.Context["bap_id"] - case "bpp": - subID = req.Context["bpp_id"] - } - ctx := context.WithValue(r.Context(), subscriberIDKey, subID) - for _, key := range cfg.ContextKeys { - value := uuid.NewString() - updatedValue := update(req.Context, key, value) - ctx = context.WithValue(ctx, contextKeyType(key), updatedValue) - } - reqData := map[string]any{"context": req.Context} - updatedBody, _ := json.Marshal(reqData) - r.Body = io.NopCloser(bytes.NewBuffer(updatedBody)) - r.ContentLength = int64(len(updatedBody)) - r = r.WithContext(ctx) - next.ServeHTTP(w, r) - }) - }, nil -} - -func update(wrapper map[string]any, key string, value any) any { - field, exists := wrapper[key] - if !exists || isEmpty(field) { - wrapper[key] = value - return value - } - - return field -} -func isEmpty(v any) bool { - switch v := v.(type) { - case string: - return v == "" - case nil: - return true - default: - return false - } -} - -func validateConfig(cfg *Config) error { - if cfg == nil { - return errors.New("config cannot be nil") - } - - // Check if ContextKeys is empty. - if len(cfg.ContextKeys) == 0 { - return errors.New("ContextKeys cannot be empty") - } - - // Validate that ContextKeys does not contain empty strings. - for _, key := range cfg.ContextKeys { - if key == "" { - return errors.New("ContextKeys cannot contain empty strings") - } - } - return nil -} diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go deleted file mode 100644 index 307a7e7..0000000 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package requestpreprocessor - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" -) - -func TestNewUUIDSetterSuccessCases(t *testing.T) { - tests := []struct { - name string - config *Config - requestBody map[string]any - expectedKeys []string - role string - }{ - { - name: "Valid keys, update missing keys with bap role", - config: &Config{ - ContextKeys: []string{"transaction_id", "message_id"}, - Role: "bap", - }, - requestBody: map[string]any{ - "context": map[string]any{ - "transaction_id": "", - "message_id": nil, - "bap_id": "bap-123", - }, - }, - expectedKeys: []string{"transaction_id", "message_id", "bap_id"}, - role: "bap", - }, - { - name: "Valid keys, do not update existing keys with bpp role", - config: &Config{ - ContextKeys: []string{"transaction_id", "message_id"}, - Role: "bpp", - }, - requestBody: map[string]any{ - "context": map[string]any{ - "transaction_id": "existing-transaction", - "message_id": "existing-message", - "bpp_id": "bpp-456", - }, - }, - expectedKeys: []string{"transaction_id", "message_id", "bpp_id"}, - role: "bpp", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - middleware, err := NewUUIDSetter(tt.config) - if err != nil { - t.Fatalf("Unexpected error while creating middleware: %v", err) - } - - bodyBytes, _ := json.Marshal(tt.requestBody) - req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) - req.Header.Set("Content-Type", "application/json") - - rec := httptest.NewRecorder() - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - w.WriteHeader(http.StatusOK) - - subID, ok := ctx.Value(subscriberIDKey).(string) - if !ok { - http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) - return - } - - response := map[string]any{"subscriber_id": subID} - if err := json.NewEncoder(w).Encode(response); err != nil { - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - }) - - middleware(dummyHandler).ServeHTTP(rec, req) - - if rec.Code != http.StatusOK { - t.Errorf("Expected status code 200, but got %d", rec.Code) - return - } - - var responseBody map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &responseBody); err != nil { - t.Fatal("Failed to unmarshal response body:", err) - } - - expectedSubIDKey := "bap_id" - if tt.role == "bpp" { - expectedSubIDKey = "bpp_id" - } - - subID, ok := responseBody["subscriber_id"].(string) - if !ok { - t.Error("subscriber_id not found in response") - return - } - - expectedSubID := tt.requestBody["context"].(map[string]any)[expectedSubIDKey] - if subID != expectedSubID { - t.Errorf("Expected subscriber_id %v, but got %v", expectedSubID, subID) - } - }) - } -} - -func TestNewUUIDSetterErrorCases(t *testing.T) { - tests := []struct { - name string - config *Config - requestBody map[string]any - expectedCode int - }{ - { - name: "Missing context key", - config: &Config{ - ContextKeys: []string{"transaction_id"}, - }, - requestBody: map[string]any{ - "otherKey": "value", - }, - expectedCode: http.StatusBadRequest, - }, - { - name: "Invalid context type", - config: &Config{ - ContextKeys: []string{"transaction_id"}, - }, - requestBody: map[string]any{ - "context": "not-a-map", - }, - expectedCode: http.StatusBadRequest, - }, - { - name: "Nil config", - config: nil, - requestBody: map[string]any{}, - expectedCode: http.StatusInternalServerError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - middleware, err := NewUUIDSetter(tt.config) - if tt.config == nil { - if err == nil { - t.Error("Expected an error for nil config, but got none") - } - return - } - if err != nil { - t.Fatalf("Unexpected error while creating middleware: %v", err) - } - - bodyBytes, _ := json.Marshal(tt.requestBody) - req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) - req.Header.Set("Content-Type", "application/json") - - rec := httptest.NewRecorder() - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - middleware(dummyHandler).ServeHTTP(rec, req) - - if rec.Code != tt.expectedCode { - t.Errorf("Expected status code %d, but got %d", tt.expectedCode, rec.Code) - } - }) - } -} diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index 9d16767..ba36715 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -9,7 +9,8 @@ import ( "path" "strings" - definition "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/log" + "github.com/beckn/beckn-onix/pkg/model" "gopkg.in/yaml.v3" ) @@ -26,7 +27,7 @@ type routingConfig struct { // Router implements Router interface. type Router struct { - rules map[string]map[string]map[string]*definition.Route // domain -> version -> endpoint -> route + rules map[string]map[string]map[string]*model.Route // domain -> version -> endpoint -> route } // RoutingRule represents a single routing rule. @@ -61,7 +62,7 @@ func New(ctx context.Context, config *Config) (*Router, func() error, error) { return nil, nil, fmt.Errorf("config cannot be nil") } router := &Router{ - rules: make(map[string]map[string]map[string]*definition.Route), + rules: make(map[string]map[string]map[string]*model.Route), } // Load rules at bootup @@ -117,20 +118,20 @@ func (r *Router) loadRules(configPath string) error { for _, rule := range config.RoutingRules { // Initialize domain map if not exists if _, ok := r.rules[rule.Domain]; !ok { - r.rules[rule.Domain] = make(map[string]map[string]*definition.Route) + r.rules[rule.Domain] = make(map[string]map[string]*model.Route) } // Initialize version map if not exists if _, ok := r.rules[rule.Domain][rule.Version]; !ok { - r.rules[rule.Domain][rule.Version] = make(map[string]*definition.Route) + r.rules[rule.Domain][rule.Version] = make(map[string]*model.Route) } // Add all endpoints for this rule for _, endpoint := range rule.Endpoints { - var route *definition.Route + var route *model.Route switch rule.TargetType { case targetTypePublisher: - route = &definition.Route{ + route = &model.Route{ TargetType: rule.TargetType, PublisherID: rule.Target.PublisherID, } @@ -139,7 +140,7 @@ func (r *Router) loadRules(configPath string) error { if err != nil { return fmt.Errorf("invalid URL in rule: %w", err) } - route = &definition.Route{ + route = &model.Route{ TargetType: rule.TargetType, URL: parsedURL, } @@ -151,7 +152,7 @@ func (r *Router) loadRules(configPath string) error { return fmt.Errorf("invalid URL in rule: %w", err) } } - route = &definition.Route{ + route = &model.Route{ TargetType: rule.TargetType, URL: parsedURL, } @@ -199,8 +200,12 @@ func validateRules(rules []routingRule) error { } // Route determines the routing destination based on the request context. -func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definition.Route, error) { +func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*model.Route, error) { + if r == nil { + log.Debug(ctx, "In Router :Router not set") + } + log.Debugf(ctx, "In Router: Routing request with url %v and body: %s", url, string(body)) // Parse the body to extract domain and version var requestBody struct { Context struct { @@ -213,10 +218,17 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definit if err := json.Unmarshal(body, &requestBody); err != nil { return nil, fmt.Errorf("error parsing request body: %w", err) } + log.Debugf(ctx, "In Router: Routing request with %v and body: %#s", url, requestBody) // Extract the endpoint from the URL endpoint := path.Base(url.Path) + if r.rules == nil { + + log.Debug(ctx, "In Router :Routing rules not set") + } + log.Debugf(ctx, "In Router :Routing rules len :%d", len(r.rules)) + // Lookup route in the optimized map domainRules, ok := r.rules[requestBody.Context.Domain] if !ok { @@ -244,7 +256,7 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definit } // handleProtocolMapping handles both BPP and BAP routing with proper URL construction -func handleProtocolMapping(route *definition.Route, requestURI, endpoint string) (*definition.Route, error) { +func handleProtocolMapping(route *model.Route, requestURI, endpoint string) (*model.Route, error) { uri := strings.TrimSpace(requestURI) var targetURL *url.URL if len(uri) != 0 { @@ -268,7 +280,7 @@ func handleProtocolMapping(route *definition.Route, requestURI, endpoint string) } } - return &definition.Route{ + return &model.Route{ TargetType: targetTypeURL, URL: targetURL, }, nil diff --git a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go deleted file mode 100644 index fb257c9..0000000 --- a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "context" - "errors" - - "github.com/beckn/beckn-onix/pkg/plugin/definition" - "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemavalidator" -) - -// schemaValidatorProvider provides instances of schemaValidator. -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 schemaDir from the config map - schemaDir, ok := config["schemaDir"] - if !ok || schemaDir == "" { - return nil, nil, errors.New("config must contain 'schemaDir'") - } - - // Create a new schemaValidator instance with the provided configuration - return schemavalidator.New(ctx, &schemavalidator.Config{ - SchemaDir: schemaDir, - }) -} - -// 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 deleted file mode 100644 index 75fdce0..0000000 --- a/pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go +++ /dev/null @@ -1,150 +0,0 @@ -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{"schemaDir": schemaDir}, - expectedError: "", - }, - } - - // Test using table-driven tests - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - vp := schemaValidatorProvider{} - schemaValidator, _, err := vp.New(tt.ctx, tt.config) - - // Ensure no error occurred - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - // Ensure the schemaValidator is not nil - if schemaValidator == nil { - t.Error("expected a non-nil schemaValidator, got nil") - } - }) - } -} - -// 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: "Config is empty", - ctx: context.Background(), - config: map[string]string{}, - expectedError: "config must contain 'schemaDir'", - }, - { - name: "schemaDir is empty", - ctx: context.Background(), - config: map[string]string{"schemaDir": ""}, - expectedError: "config must contain 'schemaDir'", - }, - { - name: "Invalid schema directory", - ctx: context.Background(), // Valid context - config: map[string]string{"schemaDir": "/invalid/dir"}, - expectedError: "failed to initialise schemaValidator: schema directory does not exist: /invalid/dir", - }, - { - name: "Nil context", - ctx: nil, // Nil context - config: map[string]string{"schemaDir": 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 deleted file mode 100644 index 85f2bf7..0000000 --- a/pkg/plugin/implementation/schemaValidator/schemaValidator.go +++ /dev/null @@ -1,197 +0,0 @@ -package schemavalidator - -import ( - "context" - "encoding/json" - "fmt" - "net/url" - "os" - "path" - "path/filepath" - "strings" - - response "github.com/beckn/beckn-onix/pkg/response" - - "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"` -} - -// SchemaValidator implements the Validator interface. -type SchemaValidator struct { - config *Config - schemaCache map[string]*jsonschema.Schema -} - -// Config struct for SchemaValidator. -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 schemaValidator: %v", err) - } - return v, nil, 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. - cxtDomain := 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(cxtDomain) - 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 []response.Error - 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, response.Error{ - Paths: path, - Message: message, - }) - } - // Return the array of schema validation errors - return &response.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 -} diff --git a/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go b/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go deleted file mode 100644 index 7264e2e..0000000 --- a/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package schemavalidator - -import ( - "context" - "net/url" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/santhosh-tekuri/jsonschema/v6" -) - -// 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 - wantErr bool - }{ - { - name: "Valid payload", - url: "http://example.com/endpoint", - payload: `{"context": {"domain": "example", "version": "1.0", "action": "endpoint"}}`, - wantErr: false, - }, - } - - // 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) - err := v.Validate(context.Background(), u, []byte(tt.payload)) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } else { - t.Logf("Test %s passed with no errors", tt.name) - } - }) - } -} - -func TestValidator_Validate_Failure(t *testing.T) { - tests := []struct { - name string - url string - payload string - wantErr string - }{ - { - name: "Invalid JSON payload", - url: "http://example.com/endpoint", - payload: `{"context": {"domain": "example", "version": "1.0"`, - wantErr: "failed to parse JSON payload", - }, - { - name: "Schema validation failure", - url: "http://example.com/endpoint", - payload: `{"context": {"domain": "example", "version": "1.0"}}`, - wantErr: "context: at '/context': missing property 'action'", - }, - { - name: "Schema not found", - url: "http://example.com/unknown_endpoint", - payload: `{"context": {"domain": "example", "version": "1.0"}}`, - wantErr: "schema not found for domain", - }, - } - - // 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) - err := v.Validate(context.Background(), u, []byte(tt.payload)) - if tt.wantErr != "" { - if err == nil { - t.Errorf("Expected error containing '%s', but got nil", tt.wantErr) - } else if !strings.Contains(err.Error(), tt.wantErr) { - t.Errorf("Expected error containing '%s', but got '%v'", tt.wantErr, err) - } else { - t.Logf("Test %s passed with expected error: %v", tt.name, err) - } - } else { - if err != nil { - t.Errorf("Unexpected error: %v", err) - } else { - t.Logf("Test %s passed with no errors", tt.name) - } - } - }) - } -} - -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") - 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", - }, - { - 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") - 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", - }, - { - 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") - 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": { - "context": { - "type": "object", - "properties": { - "domain": {"type": "string"}, - "version": {"type": "string"} - }, - "required": ["domain", "version"] - } - }, - "required": ["context"] - }`), 0644) - }, - 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 - if err := os.MkdirAll(schemaDir, 0000); err != nil { - t.Fatalf("Failed to create directory: %v", err) - } - return nil - }, - wantErr: "failed to read directory", - }, - { - name: "Valid schema directory", - setupFunc: func(schemaDir string) error { - // Create a valid schema file - validSchemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") - 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": { - "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 := &Config{SchemaDir: schemaDir} - v := &SchemaValidator{ - config: config, - schemaCache: make(map[string]*jsonschema.Schema), - } - - 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 TestValidatorNew_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 TestValidatorNewFailure(t *testing.T) { - tests := []struct { - name 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: "Failed to initialise validators", - config: &Config{ - SchemaDir: "/invalid/path", - }, - setupFunc: func(schemaDir string) error { - // Do not create the schema directory - return nil - }, - wantErr: "ailed to initialise schemaValidator: schema directory does not exist: /invalid/path", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // 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) - } - } - - // 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 { - t.Logf("Test %s passed with expected error: %v", tt.name, err) - } - }) - } -} diff --git a/pkg/plugin/implementation/signVerifier/cmd/plugin.go b/pkg/plugin/implementation/signVerifier/cmd/plugin.go deleted file mode 100644 index d260057..0000000 --- a/pkg/plugin/implementation/signVerifier/cmd/plugin.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "context" - "errors" - - "github.com/beckn/beckn-onix/pkg/plugin/definition" - "github.com/beckn/beckn-onix/pkg/plugin/implementation/signverifier" -) - -// VerifierProvider provides instances of Verifier. -type VerifierProvider struct{} - -// New initializes a new Verifier instance. -func (vp VerifierProvider) New(ctx context.Context, config map[string]string) (definition.Verifier, func() error, error) { - if ctx == nil { - return nil, nil, errors.New("context cannot be nil") - } - - return signverifier.New(ctx, &signverifier.Config{}) -} - -// Provider is the exported symbol that the plugin manager will look for. -var Provider definition.VerifierProvider = VerifierProvider{} diff --git a/pkg/plugin/implementation/signVerifier/cmd/plugin_test.go b/pkg/plugin/implementation/signVerifier/cmd/plugin_test.go deleted file mode 100644 index 85caee5..0000000 --- a/pkg/plugin/implementation/signVerifier/cmd/plugin_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "context" - "testing" -) - -// TestVerifierProviderSuccess tests successful creation of a verifier. -func TestVerifierProviderSuccess(t *testing.T) { - provider := VerifierProvider{} - - tests := []struct { - name string - ctx context.Context - config map[string]string - }{ - { - name: "Successful creation", - ctx: context.Background(), - config: map[string]string{}, - }, - { - name: "Nil context", - ctx: context.TODO(), - config: map[string]string{}, - }, - { - name: "Empty config", - ctx: context.Background(), - config: map[string]string{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - verifier, close, err := provider.New(tt.ctx, tt.config) - - if err != nil { - t.Fatalf("Expected no error, but got: %v", err) - } - if verifier == nil { - t.Fatal("Expected verifier instance to be non-nil") - } - if close != nil { - if err := close(); err != nil { - t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) - } - } - }) - } -} - -// TestVerifierProviderFailure tests cases where verifier creation should fail. -func TestVerifierProviderFailure(t *testing.T) { - provider := VerifierProvider{} - - tests := []struct { - name string - ctx context.Context - config map[string]string - wantErr bool - }{ - { - name: "Nil context failure", - ctx: nil, - config: map[string]string{}, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - verifierInstance, close, err := provider.New(tt.ctx, tt.config) - - if (err != nil) != tt.wantErr { - t.Fatalf("Expected error: %v, but got: %v", tt.wantErr, err) - } - if verifierInstance != nil { - t.Fatal("Expected verifier instance to be nil") - } - if close != nil { - if err := close(); err != nil { - t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) - } - } - - }) - } -} diff --git a/pkg/plugin/implementation/signVerifier/signVerifier.go b/pkg/plugin/implementation/signVerifier/signVerifier.go deleted file mode 100644 index b76dd6d..0000000 --- a/pkg/plugin/implementation/signVerifier/signVerifier.go +++ /dev/null @@ -1,120 +0,0 @@ -package signverifier - -import ( - "context" - "crypto/ed25519" - "encoding/base64" - "fmt" - "strconv" - "strings" - "time" - - "golang.org/x/crypto/blake2b" -) - -// Config struct for Verifier. -type Config struct { -} - -// Verifier implements the Verifier interface. -type Verifier struct { - config *Config -} - -// New creates a new Verifier instance. -func New(ctx context.Context, config *Config) (*Verifier, func() error, error) { - v := &Verifier{config: config} - - return v, v.Close, nil -} - -// Verify checks the signature for the given payload and public key. -func (v *Verifier) Verify(ctx context.Context, body []byte, header []byte, publicKeyBase64 string) (bool, error) { - createdTimestamp, expiredTimestamp, signature, err := parseAuthHeader(string(header)) - if err != nil { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return false, fmt.Errorf("error parsing header: %w", err) - } - - signatureBytes, err := base64.StdEncoding.DecodeString(signature) - if err != nil { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return false, fmt.Errorf("error decoding signature: %w", err) - } - - currentTime := time.Now().Unix() - if createdTimestamp > currentTime || currentTime > expiredTimestamp { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return false, fmt.Errorf("signature is expired or not yet valid") - } - - createdTime := time.Unix(createdTimestamp, 0) - expiredTime := time.Unix(expiredTimestamp, 0) - - signingString := hash(body, createdTime.Unix(), expiredTime.Unix()) - - decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64) - if err != nil { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return false, fmt.Errorf("error decoding public key: %w", err) - } - - if !ed25519.Verify(ed25519.PublicKey(decodedPublicKey), []byte(signingString), signatureBytes) { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return false, fmt.Errorf("signature verification failed") - } - - return true, nil -} - -// parseAuthHeader extracts signature values from the Authorization header. -func parseAuthHeader(header string) (int64, int64, string, error) { - header = strings.TrimPrefix(header, "Signature ") - - parts := strings.Split(header, ",") - signatureMap := make(map[string]string) - - for _, part := range parts { - keyValue := strings.SplitN(strings.TrimSpace(part), "=", 2) - if len(keyValue) == 2 { - key := strings.TrimSpace(keyValue[0]) - value := strings.Trim(keyValue[1], "\"") - signatureMap[key] = value - } - } - - createdTimestamp, err := strconv.ParseInt(signatureMap["created"], 10, 64) - if err != nil { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return 0, 0, "", fmt.Errorf("invalid created timestamp: %w", err) - } - - expiredTimestamp, err := strconv.ParseInt(signatureMap["expires"], 10, 64) - if err != nil { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return 0, 0, "", fmt.Errorf("invalid expires timestamp: %w", err) - } - - signature := signatureMap["signature"] - if signature == "" { - // TODO: Return appropriate error code when Error Code Handling Module is ready - return 0, 0, "", fmt.Errorf("signature missing in header") - } - - return createdTimestamp, expiredTimestamp, signature, nil -} - -// hash constructs a signing string for verification. -func hash(payload []byte, createdTimestamp, expiredTimestamp int64) string { - hasher, _ := blake2b.New512(nil) - hasher.Write(payload) - hashSum := hasher.Sum(nil) - digestB64 := base64.StdEncoding.EncodeToString(hashSum) - - return fmt.Sprintf("(created): %d\n(expires): %d\ndigest: BLAKE-512=%s", createdTimestamp, expiredTimestamp, digestB64) -} - -// Close releases resources (mock implementation returning nil). -func (v *Verifier) Close() error { - return nil -} diff --git a/pkg/plugin/implementation/signVerifier/signVerifier_test.go b/pkg/plugin/implementation/signVerifier/signVerifier_test.go deleted file mode 100644 index 0e22eb8..0000000 --- a/pkg/plugin/implementation/signVerifier/signVerifier_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package signverifier - -import ( - "context" - "crypto/ed25519" - "encoding/base64" - "strconv" - "testing" - "time" -) - -// generateTestKeyPair generates a new ED25519 key pair for testing. -func generateTestKeyPair() (string, string) { - publicKey, privateKey, _ := ed25519.GenerateKey(nil) - return base64.StdEncoding.EncodeToString(privateKey), base64.StdEncoding.EncodeToString(publicKey) -} - -// signTestData creates a valid signature for test cases. -func signTestData(privateKeyBase64 string, body []byte, createdAt, expiresAt int64) string { - privateKeyBytes, _ := base64.StdEncoding.DecodeString(privateKeyBase64) - privateKey := ed25519.PrivateKey(privateKeyBytes) - - signingString := hash(body, createdAt, expiresAt) - signature := ed25519.Sign(privateKey, []byte(signingString)) - - return base64.StdEncoding.EncodeToString(signature) -} - -// TestVerifySuccessCases tests all valid signature verification cases. -func TestVerifySuccess(t *testing.T) { - privateKeyBase64, publicKeyBase64 := generateTestKeyPair() - - tests := []struct { - name string - body []byte - createdAt int64 - expiresAt int64 - }{ - { - name: "Valid Signature", - body: []byte("Test Payload"), - createdAt: time.Now().Unix(), - expiresAt: time.Now().Unix() + 3600, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - signature := signTestData(privateKeyBase64, tt.body, tt.createdAt, tt.expiresAt) - header := `Signature created="` + strconv.FormatInt(tt.createdAt, 10) + - `", expires="` + strconv.FormatInt(tt.expiresAt, 10) + - `", signature="` + signature + `"` - - verifier, close, _ := New(context.Background(), &Config{}) - valid, err := verifier.Verify(context.Background(), tt.body, []byte(header), publicKeyBase64) - - if err != nil { - t.Fatalf("Expected no error, but got: %v", err) - } - if !valid { - t.Fatal("Expected signature verification to succeed") - } - if close != nil { - if err := close(); err != nil { - t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) - } - } - }) - } -} - -// TestVerifyFailureCases tests all invalid signature verification cases. -func TestVerifyFailure(t *testing.T) { - privateKeyBase64, publicKeyBase64 := generateTestKeyPair() - _, wrongPublicKeyBase64 := generateTestKeyPair() - - tests := []struct { - name string - body []byte - header string - pubKey string - }{ - { - name: "Missing Authorization Header", - body: []byte("Test Payload"), - header: "", - pubKey: publicKeyBase64, - }, - { - name: "Malformed Header", - body: []byte("Test Payload"), - header: `InvalidSignature created="wrong"`, - pubKey: publicKeyBase64, - }, - { - name: "Invalid Base64 Signature", - body: []byte("Test Payload"), - header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + - `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + - `", signature="!!INVALIDBASE64!!"`, - pubKey: publicKeyBase64, - }, - { - name: "Expired Signature", - body: []byte("Test Payload"), - header: `Signature created="` + strconv.FormatInt(time.Now().Unix()-7200, 10) + - `", expires="` + strconv.FormatInt(time.Now().Unix()-3600, 10) + - `", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix()-7200, time.Now().Unix()-3600) + `"`, - pubKey: publicKeyBase64, - }, - { - name: "Invalid Public Key", - body: []byte("Test Payload"), - header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + - `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + - `", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix(), time.Now().Unix()+3600) + `"`, - pubKey: wrongPublicKeyBase64, - }, - { - name: "Invalid Expires Timestamp", - body: []byte("Test Payload"), - header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + - `", expires="invalid_timestamp"`, - pubKey: publicKeyBase64, - }, - { - name: "Signature Missing in Headers", - body: []byte("Test Payload"), - header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + - `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + `"`, - pubKey: publicKeyBase64, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - verifier, close, _ := New(context.Background(), &Config{}) - valid, err := verifier.Verify(context.Background(), tt.body, []byte(tt.header), tt.pubKey) - - if err == nil { - t.Fatal("Expected an error but got none") - } - if valid { - t.Fatal("Expected verification to fail") - } - if close != nil { - if err := close(); err != nil { - t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) - } - } - }) - } -} From f0e39e34e75842c5628a15704803b85cc0964b53 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Sun, 30 Mar 2025 19:13:02 +0530 Subject: [PATCH 11/15] updated as per the review comments --- Dockerfile.adapter | 25 ++ cmd/adapter/main_test.go | 15 +- core/module/handler/config.go | 2 +- core/module/handler/stdHandler.go | 8 +- core/module/handler/step.go | 6 +- core/module/module_test.go | 2 +- pkg/model/model.go | 8 + pkg/plugin/definition/keymanager.go | 16 +- pkg/plugin/definition/publisher.go | 6 +- pkg/plugin/definition/registry.go | 11 + pkg/plugin/definition/router.go | 11 +- pkg/plugin/definition/signer.go | 7 - pkg/plugin/definition/signvalidator.go | 15 + .../implementation/decrypter/cmd/plugin.go | 8 +- .../decrypter/cmd/plugin_test.go | 2 +- .../implementation/encrypter/cmd/plugin.go | 8 +- .../encrypter/cmd/plugin_test.go | 2 +- .../reqpreprocessor/cmd/plugin.go | 24 ++ .../reqpreprocessor/cmd/plugin_test.go | 85 +++++ .../reqpreprocessor/reqpreprocessor.go | 108 ++++++ .../reqpreprocessor/reqpreprocessor_test.go | 178 +++++++++ .../implementation/router/cmd/plugin.go | 4 +- .../schemavalidator/cmd/plugin.go | 33 ++ .../schemavalidator/cmd/plugin_test.go | 150 ++++++++ .../schemavalidator/schemavalidator.go | 197 ++++++++++ .../schemavalidator/schemavalidator_test.go | 353 ++++++++++++++++++ .../implementation/signer/cmd/plugin.go | 2 +- pkg/plugin/implementation/signer/signer.go | 7 +- .../signvalidator/cmd/plugin.go | 24 ++ .../signvalidator/cmd/plugin_test.go | 89 +++++ .../signvalidator/signvalidator.go | 115 ++++++ .../signvalidator/signvalidator_test.go | 147 ++++++++ pkg/plugin/manager.go | 7 +- pkg/response/response.go | 39 -- pkg/response/response_test.go | 15 - 35 files changed, 1605 insertions(+), 124 deletions(-) create mode 100644 Dockerfile.adapter create mode 100644 pkg/plugin/definition/registry.go create mode 100644 pkg/plugin/definition/signvalidator.go create mode 100644 pkg/plugin/implementation/reqpreprocessor/cmd/plugin.go create mode 100644 pkg/plugin/implementation/reqpreprocessor/cmd/plugin_test.go create mode 100644 pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go create mode 100644 pkg/plugin/implementation/reqpreprocessor/reqpreprocessor_test.go create mode 100644 pkg/plugin/implementation/schemavalidator/cmd/plugin.go create mode 100644 pkg/plugin/implementation/schemavalidator/cmd/plugin_test.go create mode 100644 pkg/plugin/implementation/schemavalidator/schemavalidator.go create mode 100644 pkg/plugin/implementation/schemavalidator/schemavalidator_test.go create mode 100644 pkg/plugin/implementation/signvalidator/cmd/plugin.go create mode 100644 pkg/plugin/implementation/signvalidator/cmd/plugin_test.go create mode 100644 pkg/plugin/implementation/signvalidator/signvalidator.go create mode 100644 pkg/plugin/implementation/signvalidator/signvalidator_test.go diff --git a/Dockerfile.adapter b/Dockerfile.adapter new file mode 100644 index 0000000..a8eb006 --- /dev/null +++ b/Dockerfile.adapter @@ -0,0 +1,25 @@ +FROM golang:1.24-bullseye AS builder + +WORKDIR /app +COPY cmd/adapter ./cmd/adapter +COPY core/ ./core +COPY pkg/ ./pkg +COPY go.mod . +COPY go.sum . +RUN go mod download + +RUN go build -o server cmd/adapter/main.go + +# Create a minimal runtime image +FROM cgr.dev/chainguard/wolfi-base +# ✅ Alpine is removed; using minimal Debian +WORKDIR /app + +# Copy only the built binary and plugin +COPY --from=builder /app/server . + +# Expose port 8080 +EXPOSE 8080 + +# Run the Go server with the config flag from environment variable. +CMD ["sh", "-c", "./server --config=${CONFIG_FILE}"] \ No newline at end of file diff --git a/cmd/adapter/main_test.go b/cmd/adapter/main_test.go index 1a3182d..b1b15d1 100644 --- a/cmd/adapter/main_test.go +++ b/cmd/adapter/main_test.go @@ -28,7 +28,7 @@ func (m *MockPluginManager) Middleware(ctx context.Context, cfg *plugin.Config) } // SignValidator returns a mock implementation of the Verifier interface. -func (m *MockPluginManager) SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error) { +func (m *MockPluginManager) SignValidator(ctx context.Context, cfg *plugin.Config) (definition.SignValidator, error) { return nil, nil } @@ -200,7 +200,7 @@ func TestRunFailure(t *testing.T) { tests := []struct { name string configData string - mockMgr func() (*plugin.Manager, func(), error) + mockMgr func() (*MockPluginManager, func(), error) mockLogger func(cfg *Config) error mockServer func(ctx context.Context, mgr handler.PluginManager, cfg *Config) (http.Handler, error) expectedErr string @@ -208,8 +208,8 @@ func TestRunFailure(t *testing.T) { { name: "Invalid Config File", configData: "invalid_config.yaml", - mockMgr: func() (*plugin.Manager, func(), error) { - return &plugin.Manager{}, func() {}, nil + mockMgr: func() (*MockPluginManager, func(), error) { + return &MockPluginManager{}, func() {}, nil }, mockLogger: func(cfg *Config) error { return nil @@ -236,9 +236,10 @@ func TestRunFailure(t *testing.T) { // Mock dependencies originalNewManager := newManagerFunc - newManagerFunc = func(ctx context.Context, cfg *plugin.ManagerConfig) (*plugin.Manager, func(), error) { - return tt.mockMgr() - } + // newManagerFunc = func(ctx context.Context, cfg *plugin.ManagerConfig) (*plugin.Manager, func(), error) { + // return tt.mockMgr() + // } + newManagerFunc = nil defer func() { newManagerFunc = originalNewManager }() originalNewServer := newServerFunc diff --git a/core/module/handler/config.go b/core/module/handler/config.go index fa2a966..16a2c0c 100644 --- a/core/module/handler/config.go +++ b/core/module/handler/config.go @@ -12,7 +12,7 @@ import ( // PluginManager defines an interface for managing plugins dynamically. type PluginManager interface { Middleware(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) - SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error) + SignValidator(ctx context.Context, cfg *plugin.Config) (definition.SignValidator, error) Validator(ctx context.Context, cfg *plugin.Config) (definition.SchemaValidator, error) Router(ctx context.Context, cfg *plugin.Config) (definition.Router, error) Publisher(ctx context.Context, cfg *plugin.Config) (definition.Publisher, error) diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index 32222cd..1a39d51 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -21,7 +21,7 @@ import ( type stdHandler struct { signer definition.Signer steps []definition.Step - signValidator definition.Verifier + signValidator definition.SignValidator cache definition.Cache km definition.KeyManager schemaValidator definition.SchemaValidator @@ -108,13 +108,15 @@ func (h *stdHandler) subID(ctx context.Context) string { return h.SubscriberID } +var proxyFunc = proxy + // route handles request forwarding or message publishing based on the routing type. func route(ctx *model.StepContext, r *http.Request, w http.ResponseWriter, pb definition.Publisher) { log.Debugf(ctx, "Routing to ctx.Route to %#v", ctx.Route) switch ctx.Route.TargetType { case "url": log.Infof(ctx.Context, "Forwarding request to URL: %s", ctx.Route.URL) - proxy(r, w, ctx.Route.URL) + proxyFunc(r, w, ctx.Route.URL) return case "publisher": if pb == nil { @@ -124,7 +126,7 @@ func route(ctx *model.StepContext, r *http.Request, w http.ResponseWriter, pb de return } log.Infof(ctx.Context, "Publishing message to: %s", ctx.Route.PublisherID) - if err := pb.Publish(ctx, ctx.Body); err != nil { + if err := pb.Publish(ctx, ctx.Route.PublisherID, ctx.Body); err != nil { log.Errorf(ctx.Context, err, "Failed to publish message") http.Error(w, "Error publishing message", http.StatusInternalServerError) response.SendNack(ctx, w, err) diff --git a/core/module/handler/step.go b/core/module/handler/step.go index f7f8abc..9074843 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -52,12 +52,12 @@ func (s *signStep) Run(ctx *model.StepContext) error { // validateSignStep represents the signature validation step. type validateSignStep struct { - validator definition.Verifier + validator definition.SignValidator km definition.KeyManager } // newValidateSignStep initializes and returns a new validate sign step. -func newValidateSignStep(signValidator definition.Verifier, km definition.KeyManager) (definition.Step, error) { +func newValidateSignStep(signValidator definition.SignValidator, km definition.KeyManager) (definition.Step, error) { if signValidator == nil { return nil, fmt.Errorf("invalid config: SignValidator plugin not configured") } @@ -102,7 +102,7 @@ func (s *validateSignStep) validate(ctx *model.StepContext, value string) error if err != nil { return fmt.Errorf("failed to get validation key: %w", err) } - if _, err := s.validator.Verify(ctx, ctx.Body, []byte(value), key); err != nil { + if err := s.validator.Validate(ctx, ctx.Body, value, key); err != nil { return fmt.Errorf("sign validation failed: %w", err) } return nil diff --git a/core/module/module_test.go b/core/module/module_test.go index 56901f8..a4e1106 100644 --- a/core/module/module_test.go +++ b/core/module/module_test.go @@ -23,7 +23,7 @@ func (m *mockPluginManager) Middleware(ctx context.Context, cfg *plugin.Config) } // SignValidator returns a mock verifier implementation. -func (m *mockPluginManager) SignValidator(ctx context.Context, cfg *plugin.Config) (definition.Verifier, error) { +func (m *mockPluginManager) SignValidator(ctx context.Context, cfg *plugin.Config) (definition.SignValidator, error) { return nil, nil } diff --git a/pkg/model/model.go b/pkg/model/model.go index 2038d94..ec5e29c 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -86,6 +86,14 @@ type Route struct { URL *url.URL // For API calls } +type Keyset struct { + UniqueKeyID string + SigningPrivate string + SigningPublic string + EncrPrivate string + EncrPublic string +} + // StepContext holds context information for a request processing step. type StepContext struct { context.Context diff --git a/pkg/plugin/definition/keymanager.go b/pkg/plugin/definition/keymanager.go index 8d037a4..f2c0e2f 100644 --- a/pkg/plugin/definition/keymanager.go +++ b/pkg/plugin/definition/keymanager.go @@ -6,18 +6,10 @@ import ( "github.com/beckn/beckn-onix/pkg/model" ) -type Keyset struct { - UniqueKeyID string - SigningPrivate string - SigningPublic string - EncrPrivate string - EncrPublic string -} - // KeyManager defines the interface for key management operations/methods. type KeyManager interface { - GenerateKeyPairs() (*Keyset, error) - StorePrivateKeys(ctx context.Context, keyID string, keys *Keyset) error + GenerateKeyPairs() (*model.Keyset, error) + StorePrivateKeys(ctx context.Context, keyID string, keys *model.Keyset) error SigningPrivateKey(ctx context.Context, keyID string) (string, string, error) EncrPrivateKey(ctx context.Context, keyID string) (string, string, error) SigningPublicKey(ctx context.Context, subscriberID, uniqueKeyID string) (string, error) @@ -29,7 +21,3 @@ type KeyManager interface { type KeyManagerProvider interface { New(context.Context, Cache, RegistryLookup, map[string]string) (KeyManager, func() error, error) } - -type RegistryLookup interface { - Lookup(ctx context.Context, req *model.Subscription) ([]model.Subscription, error) -} diff --git a/pkg/plugin/definition/publisher.go b/pkg/plugin/definition/publisher.go index 93f9e21..4eba687 100644 --- a/pkg/plugin/definition/publisher.go +++ b/pkg/plugin/definition/publisher.go @@ -5,12 +5,10 @@ import "context" // Publisher defines the general publisher interface for messaging plugins. type Publisher interface { // Publish sends a message (as a byte slice) using the underlying messaging system. - Publish(ctx context.Context, msg []byte) error - - Close() error // Important for releasing resources. + Publish(context.Context, string, []byte) error } type PublisherProvider interface { // New initializes a new publisher instance with the given configuration. - New(ctx context.Context, config map[string]string) (Publisher, error) + New(ctx context.Context, config map[string]string) (Publisher, func(), error) } diff --git a/pkg/plugin/definition/registry.go b/pkg/plugin/definition/registry.go new file mode 100644 index 0000000..22881f3 --- /dev/null +++ b/pkg/plugin/definition/registry.go @@ -0,0 +1,11 @@ +package definition + +import ( + "context" + + "github.com/beckn/beckn-onix/pkg/model" +) + +type RegistryLookup interface { + Lookup(ctx context.Context, req *model.Subscription) ([]model.Subscription, error) +} diff --git a/pkg/plugin/definition/router.go b/pkg/plugin/definition/router.go index 05e2e30..f30a1ca 100644 --- a/pkg/plugin/definition/router.go +++ b/pkg/plugin/definition/router.go @@ -3,14 +3,9 @@ package definition import ( "context" "net/url" -) -// Route defines the structure for the Route returned. -type Route struct { - TargetType string // "url" or "msgq" or "bap" or "bpp" - PublisherID string // For message queues - URL *url.URL // For API calls -} + "github.com/beckn/beckn-onix/pkg/model" +) // RouterProvider initializes the a new Router instance with the given config. type RouterProvider interface { @@ -20,5 +15,5 @@ type RouterProvider interface { // Router defines the interface for routing requests. type Router interface { // Route determines the routing destination based on the request context. - Route(ctx context.Context, url *url.URL, body []byte) (*Route, error) + Route(ctx context.Context, url *url.URL, body []byte) (*model.Route, error) } diff --git a/pkg/plugin/definition/signer.go b/pkg/plugin/definition/signer.go index 84db5f5..eff7bae 100644 --- a/pkg/plugin/definition/signer.go +++ b/pkg/plugin/definition/signer.go @@ -8,7 +8,6 @@ type Signer interface { // The signature is created with the given timestamps: createdAt (signature creation time) // and expiresAt (signature expiration time). Sign(ctx context.Context, body []byte, privateKeyBase64 string, createdAt, expiresAt int64) (string, error) - Close() error // Close for releasing resources } // SignerProvider initializes a new signer instance with the given config. @@ -16,9 +15,3 @@ type SignerProvider interface { // New creates a new signer instance based on the provided config. New(ctx context.Context, config map[string]string) (Signer, func() error, error) } - -// PrivateKeyManager is the interface for key management plugin. -type PrivateKeyManager interface { - // PrivateKey retrieves the private key for the given subscriberID and keyID. - PrivateKey(ctx context.Context, subscriberID string, keyID string) (string, error) -} diff --git a/pkg/plugin/definition/signvalidator.go b/pkg/plugin/definition/signvalidator.go new file mode 100644 index 0000000..e900a37 --- /dev/null +++ b/pkg/plugin/definition/signvalidator.go @@ -0,0 +1,15 @@ +package definition + +import "context" + +// SignValidator defines the method for verifying signatures. +type SignValidator interface { + // Validate checks the validity of the signature for the given body. + Validate(ctx context.Context, body []byte, header string, publicKeyBase64 string) error +} + +// SignValidatorProvider initializes a new Verifier instance with the given config. +type SignValidatorProvider interface { + // New creates a new Verifier instance based on the provided config. + New(ctx context.Context, config map[string]string) (SignValidator, func() error, error) +} diff --git a/pkg/plugin/implementation/decrypter/cmd/plugin.go b/pkg/plugin/implementation/decrypter/cmd/plugin.go index cb988a9..628e2cb 100644 --- a/pkg/plugin/implementation/decrypter/cmd/plugin.go +++ b/pkg/plugin/implementation/decrypter/cmd/plugin.go @@ -7,13 +7,13 @@ import ( decrypter "github.com/beckn/beckn-onix/pkg/plugin/implementation/decrypter" ) -// DecrypterProvider implements the definition.DecrypterProvider interface. -type DecrypterProvider struct{} +// decrypterProvider implements the definition.decrypterProvider interface. +type decrypterProvider struct{} // New creates a new Decrypter instance using the provided configuration. -func (dp DecrypterProvider) New(ctx context.Context, config map[string]string) (definition.Decrypter, func() error, error) { +func (dp decrypterProvider) New(ctx context.Context, config map[string]string) (definition.Decrypter, func() error, error) { return decrypter.New(ctx) } // Provider is the exported symbol that the plugin manager will look for. -var Provider definition.DecrypterProvider = DecrypterProvider{} +var Provider = decrypterProvider{} diff --git a/pkg/plugin/implementation/decrypter/cmd/plugin_test.go b/pkg/plugin/implementation/decrypter/cmd/plugin_test.go index 6a4f168..0e8a079 100644 --- a/pkg/plugin/implementation/decrypter/cmd/plugin_test.go +++ b/pkg/plugin/implementation/decrypter/cmd/plugin_test.go @@ -25,7 +25,7 @@ func TestDecrypterProviderSuccess(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - provider := DecrypterProvider{} + provider := decrypterProvider{} decrypter, cleanup, err := provider.New(tt.ctx, tt.config) // Check error. diff --git a/pkg/plugin/implementation/encrypter/cmd/plugin.go b/pkg/plugin/implementation/encrypter/cmd/plugin.go index aad52ef..31e0044 100644 --- a/pkg/plugin/implementation/encrypter/cmd/plugin.go +++ b/pkg/plugin/implementation/encrypter/cmd/plugin.go @@ -7,12 +7,12 @@ import ( "github.com/beckn/beckn-onix/pkg/plugin/implementation/encrypter" ) -// EncrypterProvider implements the definition.EncrypterProvider interface. -type EncrypterProvider struct{} +// encrypterProvider implements the definition.encrypterProvider interface. +type encrypterProvider struct{} -func (ep EncrypterProvider) New(ctx context.Context, config map[string]string) (definition.Encrypter, func() error, error) { +func (ep encrypterProvider) New(ctx context.Context, config map[string]string) (definition.Encrypter, func() error, error) { return encrypter.New(ctx) } // Provider is the exported symbol that the plugin manager will look for. -var Provider definition.EncrypterProvider = EncrypterProvider{} +var Provider = encrypterProvider{} diff --git a/pkg/plugin/implementation/encrypter/cmd/plugin_test.go b/pkg/plugin/implementation/encrypter/cmd/plugin_test.go index cbb469e..1f65450 100644 --- a/pkg/plugin/implementation/encrypter/cmd/plugin_test.go +++ b/pkg/plugin/implementation/encrypter/cmd/plugin_test.go @@ -28,7 +28,7 @@ func TestEncrypterProviderSuccess(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create provider and encrypter. - provider := EncrypterProvider{} + provider := encrypterProvider{} encrypter, cleanup, err := provider.New(tt.ctx, tt.config) if err != nil { t.Fatalf("EncrypterProvider.New() error = %v", err) diff --git a/pkg/plugin/implementation/reqpreprocessor/cmd/plugin.go b/pkg/plugin/implementation/reqpreprocessor/cmd/plugin.go new file mode 100644 index 0000000..b89b650 --- /dev/null +++ b/pkg/plugin/implementation/reqpreprocessor/cmd/plugin.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + "net/http" + "strings" + + "github.com/beckn/beckn-onix/pkg/plugin/implementation/reqpreprocessor" +) + +type provider struct{} + +func (p provider) New(ctx context.Context, c map[string]string) (func(http.Handler) http.Handler, error) { + config := &reqpreprocessor.Config{} + if contextKeysStr, ok := c["contextKeys"]; ok { + config.ContextKeys = strings.Split(contextKeysStr, ",") + } + if role, ok := c["role"]; ok { + config.Role = role + } + return reqpreprocessor.NewPreProcessor(config) +} + +var Provider = provider{} diff --git a/pkg/plugin/implementation/reqpreprocessor/cmd/plugin_test.go b/pkg/plugin/implementation/reqpreprocessor/cmd/plugin_test.go new file mode 100644 index 0000000..6044c44 --- /dev/null +++ b/pkg/plugin/implementation/reqpreprocessor/cmd/plugin_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TODO: Will Split this into success and fail (two test cases) +func TestProviderNew(t *testing.T) { + testCases := []struct { + name string + config map[string]string + expectedError bool + expectedStatus int + prepareRequest func(req *http.Request) + }{ + { + name: "No Config", + config: map[string]string{}, + expectedError: true, + expectedStatus: http.StatusOK, + prepareRequest: func(req *http.Request) { + // Add minimal required headers. + req.Header.Set("context", "test-context") + req.Header.Set("transaction_id", "test-transaction") + }, + }, + { + name: "With Check Keys", + config: map[string]string{ + "contextKeys": "message_id,transaction_id", + }, + expectedError: false, + expectedStatus: http.StatusOK, + prepareRequest: func(req *http.Request) { + // Add headers matching the check keys. + req.Header.Set("context", "test-context") + req.Header.Set("transaction_id", "test-transaction") + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + requestBody := `{ + "context": { + "transaction_id": "abc" + } + }` + + p := provider{} + middleware, err := p.New(context.Background(), tc.config) + if tc.expectedError { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.NotNil(t, middleware) + + testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + req := httptest.NewRequest("POST", "/", strings.NewReader(requestBody)) + req.Header.Set("Content-Type", "application/json") + if tc.prepareRequest != nil { + tc.prepareRequest(req) + } + + w := httptest.NewRecorder() + middlewaredHandler := middleware(testHandler) + middlewaredHandler.ServeHTTP(w, req) + assert.Equal(t, tc.expectedStatus, w.Code, "Unexpected response status") + responseBody := w.Body.String() + t.Logf("Response Body: %s", responseBody) + + }) + } +} diff --git a/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go b/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go new file mode 100644 index 0000000..020df4d --- /dev/null +++ b/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go @@ -0,0 +1,108 @@ +package reqpreprocessor + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/beckn/beckn-onix/pkg/log" + "github.com/google/uuid" +) + +type Config struct { + ContextKeys []string + Role string +} + +type becknRequest struct { + Context map[string]any `json:"context"` +} + +const contextKey = "context" +const subscriberIDKey = "subscriber_id" + +func NewPreProcessor(cfg *Config) (func(http.Handler) http.Handler, error) { + if err := validateConfig(cfg); err != nil { + return nil, err + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + var req becknRequest + ctx := r.Context() + if err := json.Unmarshal(body, &req); err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return + } + if req.Context == nil { + http.Error(w, fmt.Sprintf("%s field not found.", contextKey), http.StatusBadRequest) + return + } + var subID any + switch cfg.Role { + case "bap": + subID = req.Context["bap_id"] + case "bpp": + subID = req.Context["bpp_id"] + } + if subID != nil { + log.Debugf(ctx, "adding subscriberId to request:%s, %v", subscriberIDKey, subID) + ctx = context.WithValue(ctx, subscriberIDKey, subID) + } + for _, key := range cfg.ContextKeys { + value := uuid.NewString() + updatedValue := update(req.Context, key, value) + ctx = context.WithValue(ctx, key, updatedValue) + } + reqData := map[string]any{"context": req.Context} + updatedBody, _ := json.Marshal(reqData) + r.Body = io.NopCloser(bytes.NewBuffer(updatedBody)) + r.ContentLength = int64(len(updatedBody)) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + }) + }, nil +} + +func update(wrapper map[string]any, key string, value any) any { + field, exists := wrapper[key] + if !exists || isEmpty(field) { + wrapper[key] = value + return value + } + + return field +} +func isEmpty(v any) bool { + switch v := v.(type) { + case string: + return v == "" + case nil: + return true + default: + return false + } +} + +func validateConfig(cfg *Config) error { + if cfg == nil { + return errors.New("config cannot be nil") + } + + // Check if ContextKeys is empty. + if len(cfg.ContextKeys) == 0 { + return errors.New("ContextKeys cannot be empty") + } + + // Validate that ContextKeys does not contain empty strings. + for _, key := range cfg.ContextKeys { + if key == "" { + return errors.New("ContextKeys cannot contain empty strings") + } + } + return nil +} diff --git a/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor_test.go b/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor_test.go new file mode 100644 index 0000000..d70af8e --- /dev/null +++ b/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor_test.go @@ -0,0 +1,178 @@ +package reqpreprocessor + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" +) + +func TestNewUUIDSetterSuccessCases(t *testing.T) { + tests := []struct { + name string + config *Config + requestBody map[string]any + expectedKeys []string + role string + }{ + { + name: "Valid keys, update missing keys with bap role", + config: &Config{ + ContextKeys: []string{"transaction_id", "message_id"}, + Role: "bap", + }, + requestBody: map[string]any{ + "context": map[string]any{ + "transaction_id": "", + "message_id": nil, + "bap_id": "bap-123", + }, + }, + expectedKeys: []string{"transaction_id", "message_id", "bap_id"}, + role: "bap", + }, + { + name: "Valid keys, do not update existing keys with bpp role", + config: &Config{ + ContextKeys: []string{"transaction_id", "message_id"}, + Role: "bpp", + }, + requestBody: map[string]any{ + "context": map[string]any{ + "transaction_id": "existing-transaction", + "message_id": "existing-message", + "bpp_id": "bpp-456", + }, + }, + expectedKeys: []string{"transaction_id", "message_id", "bpp_id"}, + role: "bpp", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + middleware, err := NewPreProcessor(tt.config) + if err != nil { + t.Fatalf("Unexpected error while creating middleware: %v", err) + } + + bodyBytes, _ := json.Marshal(tt.requestBody) + req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + w.WriteHeader(http.StatusOK) + + subID, ok := ctx.Value(subscriberIDKey).(string) + if !ok { + http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) + return + } + + response := map[string]any{"subscriber_id": subID} + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + }) + + middleware(dummyHandler).ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("Expected status code 200, but got %d", rec.Code) + return + } + + var responseBody map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &responseBody); err != nil { + t.Fatal("Failed to unmarshal response body:", err) + } + + expectedSubIDKey := "bap_id" + if tt.role == "bpp" { + expectedSubIDKey = "bpp_id" + } + + subID, ok := responseBody["subscriber_id"].(string) + if !ok { + t.Error("subscriber_id not found in response") + return + } + + expectedSubID := tt.requestBody["context"].(map[string]any)[expectedSubIDKey] + if subID != expectedSubID { + t.Errorf("Expected subscriber_id %v, but got %v", expectedSubID, subID) + } + }) + } +} + +func TestNewUUIDSetterErrorCases(t *testing.T) { + tests := []struct { + name string + config *Config + requestBody map[string]any + expectedCode int + }{ + { + name: "Missing context key", + config: &Config{ + ContextKeys: []string{"transaction_id"}, + }, + requestBody: map[string]any{ + "otherKey": "value", + }, + expectedCode: http.StatusBadRequest, + }, + { + name: "Invalid context type", + config: &Config{ + ContextKeys: []string{"transaction_id"}, + }, + requestBody: map[string]any{ + "context": "not-a-map", + }, + expectedCode: http.StatusBadRequest, + }, + { + name: "Nil config", + config: nil, + requestBody: map[string]any{}, + expectedCode: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + middleware, err := NewPreProcessor(tt.config) + if tt.config == nil { + if err == nil { + t.Error("Expected an error for nil config, but got none") + } + return + } + if err != nil { + t.Fatalf("Unexpected error while creating middleware: %v", err) + } + + bodyBytes, _ := json.Marshal(tt.requestBody) + req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + middleware(dummyHandler).ServeHTTP(rec, req) + + if rec.Code != tt.expectedCode { + t.Errorf("Expected status code %d, but got %d", tt.expectedCode, rec.Code) + } + }) + } +} diff --git a/pkg/plugin/implementation/router/cmd/plugin.go b/pkg/plugin/implementation/router/cmd/plugin.go index 556f129..d5d71e3 100644 --- a/pkg/plugin/implementation/router/cmd/plugin.go +++ b/pkg/plugin/implementation/router/cmd/plugin.go @@ -4,8 +4,8 @@ import ( "context" "errors" - definition "github.com/beckn/beckn-onix/pkg/plugin/definition" - router "github.com/beckn/beckn-onix/pkg/plugin/implementation/router" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/router" ) // RouterProvider provides instances of Router. diff --git a/pkg/plugin/implementation/schemavalidator/cmd/plugin.go b/pkg/plugin/implementation/schemavalidator/cmd/plugin.go new file mode 100644 index 0000000..f71aaaf --- /dev/null +++ b/pkg/plugin/implementation/schemavalidator/cmd/plugin.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "errors" + + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemavalidator" +) + +// schemaValidatorProvider provides instances of schemaValidator. +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 schemaDir from the config map + schemaDir, ok := config["schemaDir"] + if !ok || schemaDir == "" { + return nil, nil, errors.New("config must contain 'schemaDir'") + } + + // Create a new schemaValidator instance with the provided configuration + return schemavalidator.New(ctx, &schemavalidator.Config{ + SchemaDir: schemaDir, + }) +} + +// Provider is the exported symbol that the plugin manager will look for. +var Provider = 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..75fdce0 --- /dev/null +++ b/pkg/plugin/implementation/schemavalidator/cmd/plugin_test.go @@ -0,0 +1,150 @@ +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{"schemaDir": schemaDir}, + expectedError: "", + }, + } + + // Test using table-driven tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + vp := schemaValidatorProvider{} + schemaValidator, _, err := vp.New(tt.ctx, tt.config) + + // Ensure no error occurred + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // Ensure the schemaValidator is not nil + if schemaValidator == nil { + t.Error("expected a non-nil schemaValidator, got nil") + } + }) + } +} + +// 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: "Config is empty", + ctx: context.Background(), + config: map[string]string{}, + expectedError: "config must contain 'schemaDir'", + }, + { + name: "schemaDir is empty", + ctx: context.Background(), + config: map[string]string{"schemaDir": ""}, + expectedError: "config must contain 'schemaDir'", + }, + { + name: "Invalid schema directory", + ctx: context.Background(), // Valid context + config: map[string]string{"schemaDir": "/invalid/dir"}, + expectedError: "failed to initialise schemaValidator: schema directory does not exist: /invalid/dir", + }, + { + name: "Nil context", + ctx: nil, // Nil context + config: map[string]string{"schemaDir": 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..715def7 --- /dev/null +++ b/pkg/plugin/implementation/schemavalidator/schemavalidator.go @@ -0,0 +1,197 @@ +package schemavalidator + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/beckn/beckn-onix/pkg/model" + + "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"` +} + +// schemaValidator implements the Validator interface. +type schemaValidator struct { + config *Config + schemaCache map[string]*jsonschema.Schema +} + +// Config struct for SchemaValidator. +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 schemaValidator: %v", err) + } + return v, nil, 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. + cxtDomain := 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(cxtDomain) + 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 []model.Error + 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, model.Error{ + Paths: path, + Message: message, + }) + } + // Return the array of schema validation errors + return &model.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 +} diff --git a/pkg/plugin/implementation/schemavalidator/schemavalidator_test.go b/pkg/plugin/implementation/schemavalidator/schemavalidator_test.go new file mode 100644 index 0000000..bdb4201 --- /dev/null +++ b/pkg/plugin/implementation/schemavalidator/schemavalidator_test.go @@ -0,0 +1,353 @@ +package schemavalidator + +import ( + "context" + "net/url" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/santhosh-tekuri/jsonschema/v6" +) + +// 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 + wantErr bool + }{ + { + name: "Valid payload", + url: "http://example.com/endpoint", + payload: `{"context": {"domain": "example", "version": "1.0", "action": "endpoint"}}`, + wantErr: false, + }, + } + + // 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) + err := v.Validate(context.Background(), u, []byte(tt.payload)) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + t.Logf("Test %s passed with no errors", tt.name) + } + }) + } +} + +func TestValidator_Validate_Failure(t *testing.T) { + tests := []struct { + name string + url string + payload string + wantErr string + }{ + { + name: "Invalid JSON payload", + url: "http://example.com/endpoint", + payload: `{"context": {"domain": "example", "version": "1.0"`, + wantErr: "failed to parse JSON payload", + }, + { + name: "Schema validation failure", + url: "http://example.com/endpoint", + payload: `{"context": {"domain": "example", "version": "1.0"}}`, + wantErr: "context: at '/context': missing property 'action'", + }, + { + name: "Schema not found", + url: "http://example.com/unknown_endpoint", + payload: `{"context": {"domain": "example", "version": "1.0"}}`, + wantErr: "schema not found for domain", + }, + } + + // 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) + err := v.Validate(context.Background(), u, []byte(tt.payload)) + if tt.wantErr != "" { + if err == nil { + t.Errorf("Expected error containing '%s', but got nil", tt.wantErr) + } else if !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("Expected error containing '%s', but got '%v'", tt.wantErr, err) + } else { + t.Logf("Test %s passed with expected error: %v", tt.name, err) + } + } else { + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else { + t.Logf("Test %s passed with no errors", tt.name) + } + } + }) + } +} + +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") + 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", + }, + { + 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") + 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", + }, + { + 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") + 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": { + "context": { + "type": "object", + "properties": { + "domain": {"type": "string"}, + "version": {"type": "string"} + }, + "required": ["domain", "version"] + } + }, + "required": ["context"] + }`), 0644) + }, + 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 + if err := os.MkdirAll(schemaDir, 0000); err != nil { + t.Fatalf("Failed to create directory: %v", err) + } + return nil + }, + wantErr: "failed to read directory", + }, + { + name: "Valid schema directory", + setupFunc: func(schemaDir string) error { + // Create a valid schema file + validSchemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") + 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": { + "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 := &Config{SchemaDir: schemaDir} + v := &schemaValidator{ + config: config, + schemaCache: make(map[string]*jsonschema.Schema), + } + + 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 TestValidatorNew_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 TestValidatorNewFailure(t *testing.T) { + tests := []struct { + name 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: "Failed to initialise validators", + config: &Config{ + SchemaDir: "/invalid/path", + }, + setupFunc: func(schemaDir string) error { + // Do not create the schema directory + return nil + }, + wantErr: "ailed to initialise schemaValidator: schema directory does not exist: /invalid/path", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // 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) + } + } + + // 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 { + t.Logf("Test %s passed with expected error: %v", tt.name, err) + } + }) + } +} diff --git a/pkg/plugin/implementation/signer/cmd/plugin.go b/pkg/plugin/implementation/signer/cmd/plugin.go index 2d78d98..1df515f 100644 --- a/pkg/plugin/implementation/signer/cmd/plugin.go +++ b/pkg/plugin/implementation/signer/cmd/plugin.go @@ -21,4 +21,4 @@ func (p SignerProvider) New(ctx context.Context, config map[string]string) (defi } // Provider is the exported symbol that the plugin manager will look for. -var Provider definition.SignerProvider = SignerProvider{} +var Provider = SignerProvider{} diff --git a/pkg/plugin/implementation/signer/signer.go b/pkg/plugin/implementation/signer/signer.go index c1f2af9..66015e4 100644 --- a/pkg/plugin/implementation/signer/signer.go +++ b/pkg/plugin/implementation/signer/signer.go @@ -23,7 +23,7 @@ type Signer struct { func New(ctx context.Context, config *Config) (*Signer, func() error, error) { s := &Signer{config: config} - return s, s.Close, nil + return s, nil, nil } // hash generates a signing string using BLAKE-512 hashing. @@ -70,8 +70,3 @@ func (s *Signer) Sign(ctx context.Context, body []byte, privateKeyBase64 string, return base64.StdEncoding.EncodeToString(signature), nil } - -// Close releases resources (mock implementation returning nil). -func (s *Signer) Close() error { - return nil -} diff --git a/pkg/plugin/implementation/signvalidator/cmd/plugin.go b/pkg/plugin/implementation/signvalidator/cmd/plugin.go new file mode 100644 index 0000000..947f956 --- /dev/null +++ b/pkg/plugin/implementation/signvalidator/cmd/plugin.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + "errors" + + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/signvalidator" +) + +// provider provides instances of Verifier. +type provider struct{} + +// New initializes a new Verifier instance. +func (vp provider) New(ctx context.Context, config map[string]string) (definition.SignValidator, func() error, error) { + if ctx == nil { + return nil, nil, errors.New("context cannot be nil") + } + + return signvalidator.New(ctx, &signvalidator.Config{}) +} + +// Provider is the exported symbol that the plugin manager will look for. +var Provider = provider{} diff --git a/pkg/plugin/implementation/signvalidator/cmd/plugin_test.go b/pkg/plugin/implementation/signvalidator/cmd/plugin_test.go new file mode 100644 index 0000000..a001ebf --- /dev/null +++ b/pkg/plugin/implementation/signvalidator/cmd/plugin_test.go @@ -0,0 +1,89 @@ +package main + +import ( + "context" + "testing" +) + +// TestVerifierProviderSuccess tests successful creation of a verifier. +func TestVerifierProviderSuccess(t *testing.T) { + provider := provider{} + + tests := []struct { + name string + ctx context.Context + config map[string]string + }{ + { + name: "Successful creation", + ctx: context.Background(), + config: map[string]string{}, + }, + { + name: "Nil context", + ctx: context.TODO(), + config: map[string]string{}, + }, + { + name: "Empty config", + ctx: context.Background(), + config: map[string]string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + verifier, close, err := provider.New(tt.ctx, tt.config) + + if err != nil { + t.Fatalf("Expected no error, but got: %v", err) + } + if verifier == nil { + t.Fatal("Expected verifier instance to be non-nil") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + }) + } +} + +// TestVerifierProviderFailure tests cases where verifier creation should fail. +func TestVerifierProviderFailure(t *testing.T) { + provider := provider{} + + tests := []struct { + name string + ctx context.Context + config map[string]string + wantErr bool + }{ + { + name: "Nil context failure", + ctx: nil, + config: map[string]string{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + verifierInstance, close, err := provider.New(tt.ctx, tt.config) + + if (err != nil) != tt.wantErr { + t.Fatalf("Expected error: %v, but got: %v", tt.wantErr, err) + } + if verifierInstance != nil { + t.Fatal("Expected verifier instance to be nil") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + + }) + } +} diff --git a/pkg/plugin/implementation/signvalidator/signvalidator.go b/pkg/plugin/implementation/signvalidator/signvalidator.go new file mode 100644 index 0000000..c381d40 --- /dev/null +++ b/pkg/plugin/implementation/signvalidator/signvalidator.go @@ -0,0 +1,115 @@ +package signvalidator + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "fmt" + "strconv" + "strings" + "time" + + "golang.org/x/crypto/blake2b" +) + +// Config struct for Verifier. +type Config struct { +} + +// validator implements the validator interface. +type validator struct { + config *Config +} + +// New creates a new Verifier instance. +func New(ctx context.Context, config *Config) (*validator, func() error, error) { + v := &validator{config: config} + + return v, nil, nil +} + +// Verify checks the signature for the given payload and public key. +func (v *validator) Validate(ctx context.Context, body []byte, header string, publicKeyBase64 string) error { + createdTimestamp, expiredTimestamp, signature, err := parseAuthHeader(header) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return fmt.Errorf("error parsing header: %w", err) + } + + signatureBytes, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return fmt.Errorf("error decoding signature: %w", err) + } + + currentTime := time.Now().Unix() + if createdTimestamp > currentTime || currentTime > expiredTimestamp { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return fmt.Errorf("signature is expired or not yet valid") + } + + createdTime := time.Unix(createdTimestamp, 0) + expiredTime := time.Unix(expiredTimestamp, 0) + + signingString := hash(body, createdTime.Unix(), expiredTime.Unix()) + + decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return fmt.Errorf("error decoding public key: %w", err) + } + + if !ed25519.Verify(ed25519.PublicKey(decodedPublicKey), []byte(signingString), signatureBytes) { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return fmt.Errorf("signature verification failed") + } + + return nil +} + +// parseAuthHeader extracts signature values from the Authorization header. +func parseAuthHeader(header string) (int64, int64, string, error) { + header = strings.TrimPrefix(header, "Signature ") + + parts := strings.Split(header, ",") + signatureMap := make(map[string]string) + + for _, part := range parts { + keyValue := strings.SplitN(strings.TrimSpace(part), "=", 2) + if len(keyValue) == 2 { + key := strings.TrimSpace(keyValue[0]) + value := strings.Trim(keyValue[1], "\"") + signatureMap[key] = value + } + } + + createdTimestamp, err := strconv.ParseInt(signatureMap["created"], 10, 64) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return 0, 0, "", fmt.Errorf("invalid created timestamp: %w", err) + } + + expiredTimestamp, err := strconv.ParseInt(signatureMap["expires"], 10, 64) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return 0, 0, "", fmt.Errorf("invalid expires timestamp: %w", err) + } + + signature := signatureMap["signature"] + if signature == "" { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return 0, 0, "", fmt.Errorf("signature missing in header") + } + + return createdTimestamp, expiredTimestamp, signature, nil +} + +// hash constructs a signing string for verification. +func hash(payload []byte, createdTimestamp, expiredTimestamp int64) string { + hasher, _ := blake2b.New512(nil) + hasher.Write(payload) + hashSum := hasher.Sum(nil) + digestB64 := base64.StdEncoding.EncodeToString(hashSum) + + return fmt.Sprintf("(created): %d\n(expires): %d\ndigest: BLAKE-512=%s", createdTimestamp, expiredTimestamp, digestB64) +} diff --git a/pkg/plugin/implementation/signvalidator/signvalidator_test.go b/pkg/plugin/implementation/signvalidator/signvalidator_test.go new file mode 100644 index 0000000..160d28b --- /dev/null +++ b/pkg/plugin/implementation/signvalidator/signvalidator_test.go @@ -0,0 +1,147 @@ +package signvalidator + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "strconv" + "testing" + "time" +) + +// generateTestKeyPair generates a new ED25519 key pair for testing. +func generateTestKeyPair() (string, string) { + publicKey, privateKey, _ := ed25519.GenerateKey(nil) + return base64.StdEncoding.EncodeToString(privateKey), base64.StdEncoding.EncodeToString(publicKey) +} + +// signTestData creates a valid signature for test cases. +func signTestData(privateKeyBase64 string, body []byte, createdAt, expiresAt int64) string { + privateKeyBytes, _ := base64.StdEncoding.DecodeString(privateKeyBase64) + privateKey := ed25519.PrivateKey(privateKeyBytes) + + signingString := hash(body, createdAt, expiresAt) + signature := ed25519.Sign(privateKey, []byte(signingString)) + + return base64.StdEncoding.EncodeToString(signature) +} + +// TestVerifySuccessCases tests all valid signature verification cases. +func TestVerifySuccess(t *testing.T) { + privateKeyBase64, publicKeyBase64 := generateTestKeyPair() + + tests := []struct { + name string + body []byte + createdAt int64 + expiresAt int64 + }{ + { + name: "Valid Signature", + body: []byte("Test Payload"), + createdAt: time.Now().Unix(), + expiresAt: time.Now().Unix() + 3600, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + signature := signTestData(privateKeyBase64, tt.body, tt.createdAt, tt.expiresAt) + header := `Signature created="` + strconv.FormatInt(tt.createdAt, 10) + + `", expires="` + strconv.FormatInt(tt.expiresAt, 10) + + `", signature="` + signature + `"` + + verifier, close, _ := New(context.Background(), &Config{}) + err := verifier.Validate(context.Background(), tt.body, header, publicKeyBase64) + + if err != nil { + t.Fatalf("Expected no error, but got: %v", err) + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + }) + } +} + +// TestVerifyFailureCases tests all invalid signature verification cases. +func TestVerifyFailure(t *testing.T) { + privateKeyBase64, publicKeyBase64 := generateTestKeyPair() + _, wrongPublicKeyBase64 := generateTestKeyPair() + + tests := []struct { + name string + body []byte + header string + pubKey string + }{ + { + name: "Missing Authorization Header", + body: []byte("Test Payload"), + header: "", + pubKey: publicKeyBase64, + }, + { + name: "Malformed Header", + body: []byte("Test Payload"), + header: `InvalidSignature created="wrong"`, + pubKey: publicKeyBase64, + }, + { + name: "Invalid Base64 Signature", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + + `", signature="!!INVALIDBASE64!!"`, + pubKey: publicKeyBase64, + }, + { + name: "Expired Signature", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix()-7200, 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()-3600, 10) + + `", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix()-7200, time.Now().Unix()-3600) + `"`, + pubKey: publicKeyBase64, + }, + { + name: "Invalid Public Key", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + + `", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix(), time.Now().Unix()+3600) + `"`, + pubKey: wrongPublicKeyBase64, + }, + { + name: "Invalid Expires Timestamp", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="invalid_timestamp"`, + pubKey: publicKeyBase64, + }, + { + name: "Signature Missing in Headers", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + `"`, + pubKey: publicKeyBase64, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + verifier, close, _ := New(context.Background(), &Config{}) + err := verifier.Validate(context.Background(), tt.body, tt.header, tt.pubKey) + + if err == nil { + t.Fatal("Expected an error but got none") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + }) + } +} diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 2bf8c20..23b517e 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -112,10 +112,11 @@ func (m *Manager) Publisher(ctx context.Context, cfg *Config) (definition.Publis if err != nil { return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) } - p, err := pp.New(ctx, cfg.Config) + p, closer, err := pp.New(ctx, cfg.Config) if err != nil { return nil, err } + m.addCloser(closer) return p, nil } @@ -256,8 +257,8 @@ func (m *Manager) Decryptor(ctx context.Context, cfg *Config) (definition.Decryp return decrypter, nil } -func (m *Manager) SignValidator(ctx context.Context, cfg *Config) (definition.Verifier, error) { - svp, err := provider[definition.VerifierProvider](m.plugins, cfg.ID) +func (m *Manager) SignValidator(ctx context.Context, cfg *Config) (definition.SignValidator, error) { + svp, err := provider[definition.SignValidatorProvider](m.plugins, cfg.ID) if err != nil { return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) } diff --git a/pkg/response/response.go b/pkg/response/response.go index c72b475..a5ab0c4 100644 --- a/pkg/response/response.go +++ b/pkg/response/response.go @@ -7,48 +7,9 @@ import ( "fmt" "net/http" - "strings" - "github.com/beckn/beckn-onix/pkg/model" ) -// Error represents a standardized error response used across the system. -type Error struct { - // Code is a short, machine-readable error code. - Code string `json:"code,omitempty"` - - // Message provides a human-readable description of the error. - Message string `json:"message,omitempty"` - - // Paths indicates the specific field(s) or endpoint(s) related to the error. - Paths string `json:"paths,omitempty"` -} - -// SchemaValidationErr represents a collection of schema validation failures. -type SchemaValidationErr struct { - Errors []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.Paths, err.Message)) - } - return strings.Join(errorMessages, "; ") -} - -// Message represents a standard message structure with acknowledgment and error information. -type Message struct { - // Ack contains the acknowledgment status of the response. - Ack struct { - Status string `json:"status,omitempty"` - } `json:"ack,omitempty"` - - // Error holds error details if any occurred during processing. - Error *Error `json:"error,omitempty"` -} - // SendAck sends an acknowledgment response (ACK) to the client. func SendAck(w http.ResponseWriter) { resp := &model.Response{ diff --git a/pkg/response/response_test.go b/pkg/response/response_test.go index 8b7f748..96f1caa 100644 --- a/pkg/response/response_test.go +++ b/pkg/response/response_test.go @@ -126,21 +126,6 @@ func TestSendNack(t *testing.T) { } } -func TestSchemaValidationErr_Error(t *testing.T) { - // Create sample validation errors - validationErrors := []Error{ - {Paths: "name", Message: "Name is required"}, - {Paths: "email", Message: "Invalid email format"}, - } - err := SchemaValidationErr{Errors: validationErrors} - expected := "name: Name is required; email: Invalid email format" - if err.Error() != expected { - t.Errorf("err.Error() = %s, want %s", - err.Error(), expected) - - } -} - func compareJSON(expected, actual map[string]interface{}) bool { expectedBytes, _ := json.Marshal(expected) actualBytes, _ := json.Marshal(actual) From bbb1dbc843d508c856a50de40891a181ec7c8c5e Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Mon, 31 Mar 2025 22:40:59 +0530 Subject: [PATCH 12/15] Update on the review comments --- core/module/client/registery.go | 24 ++-- core/module/client/registry_test.go | 132 +++++++----------- core/module/handler/stdHandler.go | 2 +- core/module/handler/step.go | 19 ++- go.mod | 2 +- pkg/model/model.go | 13 +- .../reqpreprocessor/reqpreprocessor.go | 8 +- pkg/plugin/implementation/router/router.go | 13 +- pkg/plugin/manager.go | 55 ++++++-- 9 files changed, 139 insertions(+), 129 deletions(-) diff --git a/core/module/client/registery.go b/core/module/client/registery.go index 57b8e71..3045ebb 100644 --- a/core/module/client/registery.go +++ b/core/module/client/registery.go @@ -21,22 +21,22 @@ type Config struct { RetryWaitMax time.Duration } -// RegisteryClient encapsulates the logic for calling the subscribe and lookup endpoints. -type RegisteryClient struct { - Config *Config - Client *retryablehttp.Client +// registryClient encapsulates the logic for calling the subscribe and lookup endpoints. +type registryClient struct { + config *Config + client *retryablehttp.Client } // NewRegisteryClient creates a new instance of Client. -func NewRegisteryClient(config *Config) *RegisteryClient { +func NewRegisteryClient(config *Config) *registryClient { retryClient := retryablehttp.NewClient() - return &RegisteryClient{Config: config, Client: retryClient} + return ®istryClient{config: config, client: retryClient} } // Subscribe calls the /subscribe endpoint with retry. -func (c *RegisteryClient) Subscribe(ctx context.Context, subscription *model.Subscription) error { - subscribeURL := fmt.Sprintf("%s/subscribe", c.Config.RegisteryURL) +func (c *registryClient) Subscribe(ctx context.Context, subscription *model.Subscription) error { + subscribeURL := fmt.Sprintf("%s/subscribe", c.config.RegisteryURL) jsonData, err := json.Marshal(subscription) if err != nil { @@ -49,7 +49,7 @@ func (c *RegisteryClient) Subscribe(ctx context.Context, subscription *model.Sub } req.Header.Set("Content-Type", "application/json") - resp, err := c.Client.Do(req) + resp, err := c.client.Do(req) if err != nil { return fmt.Errorf("failed to send request with retry: %w", err) } @@ -62,8 +62,8 @@ func (c *RegisteryClient) Subscribe(ctx context.Context, subscription *model.Sub } // Lookup calls the /lookup endpoint with retry and returns a slice of Subscription. -func (c *RegisteryClient) Lookup(ctx context.Context, subscription *model.Subscription) ([]model.Subscription, error) { - lookupURL := fmt.Sprintf("%s/lookUp", c.Config.RegisteryURL) +func (c *registryClient) Lookup(ctx context.Context, subscription *model.Subscription) ([]model.Subscription, error) { + lookupURL := fmt.Sprintf("%s/lookUp", c.config.RegisteryURL) jsonData, err := json.Marshal(subscription) if err != nil { @@ -76,7 +76,7 @@ func (c *RegisteryClient) Lookup(ctx context.Context, subscription *model.Subscr } req.Header.Set("Content-Type", "application/json") - resp, err := c.Client.Do(req) + resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("failed to send request with retry: %w", err) } diff --git a/core/module/client/registry_test.go b/core/module/client/registry_test.go index f8e7eaf..5185597 100644 --- a/core/module/client/registry_test.go +++ b/core/module/client/registry_test.go @@ -46,42 +46,37 @@ func TestSubscribeSuccess(t *testing.T) { ValidUntil: time.Now().Add(24 * time.Hour), Status: "SUBSCRIBED", } - err := client.Subscribe(context.Background(), subscription) - require.NoError(t, err) + if err != nil { + t.Fatalf("Subscribe() failed with error: %v", err) + } } -// TestSubscribeFailureWithMock tests different failure scenarios using a mock client. -func TestSubscribeFailureWithMock(t *testing.T) { +// TestSubscribeFailure tests different failure scenarios using a mock client. +func TestSubscribeFailure(t *testing.T) { tests := []struct { - name string - mockError error - expectError bool + name string + mockError error }{ { - name: "Failed subscription - Internal Server Error", - mockError: errors.New("internal server error"), - expectError: true, + name: "Failed subscription - Internal Server Error", + mockError: errors.New("internal server error"), }, { - name: "Failed subscription - Bad Request", - mockError: errors.New("bad request"), - expectError: true, + name: "Failed subscription - Bad Request", + mockError: errors.New("bad request"), }, { - name: "Request Timeout", - mockError: context.DeadlineExceeded, - expectError: true, + name: "Request Timeout", + mockError: context.DeadlineExceeded, }, { - name: "Network Failure", - mockError: errors.New("network failure"), - expectError: true, + name: "Network Failure", + mockError: errors.New("network failure"), }, { - name: "JSON Marshalling Failure", - mockError: errors.New("json marshalling failure"), - expectError: true, + name: "JSON Marshalling Failure", + mockError: errors.New("json marshalling failure"), }, } @@ -103,67 +98,21 @@ func TestSubscribeFailureWithMock(t *testing.T) { } if tt.name == "JSON Marshalling Failure" { - invalidSubscription := &model.Subscription{} - invalidSubscription.ValidFrom = time.Unix(0, 0) // Invalid zero timestamp - subscription = invalidSubscription + subscription = &model.Subscription{} // Example of an invalid object } err := mockClient.Subscribe(context.Background(), subscription) - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } + require.Error(t, err) // Directly checking for an error since all cases should fail }) } } // TestLookupSuccess tests successful lookup scenarios. func TestLookupSuccess(t *testing.T) { - tests := []struct { - name string - responseBody interface{} - responseCode int - }{ - { - name: "Successful lookup", - responseBody: []model.Subscription{ - { - Subscriber: model.Subscriber{ - SubscriberID: "123", - }, - KeyID: "test-key", - SigningPublicKey: "test-signing-key", - EncrPublicKey: "test-encryption-key", - ValidFrom: time.Now(), - ValidUntil: time.Now().Add(24 * time.Hour), - Status: "SUBSCRIBED", - }, - }, - responseCode: http.StatusOK, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(tc.responseCode) - if tc.responseBody != nil { - bodyBytes, _ := json.Marshal(tc.responseBody) - w.Write(bodyBytes) - } - })) - defer server.Close() - - config := &Config{ - RegisteryURL: server.URL, - RetryMax: 1, - RetryWaitMin: 1 * time.Millisecond, - RetryWaitMax: 2 * time.Millisecond, - } - rClient := NewRegisteryClient(config) - ctx := context.Background() - subscription := &model.Subscription{ + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + response := []model.Subscription{ + { Subscriber: model.Subscriber{ SubscriberID: "123", }, @@ -173,14 +122,37 @@ func TestLookupSuccess(t *testing.T) { ValidFrom: time.Now(), ValidUntil: time.Now().Add(24 * time.Hour), Status: "SUBSCRIBED", - } + }, + } + bodyBytes, _ := json.Marshal(response) + w.Write(bodyBytes) + })) + defer server.Close() - result, err := rClient.Lookup(ctx, subscription) - require.NoError(t, err) - require.NotEmpty(t, result) - require.Equal(t, subscription.Subscriber.SubscriberID, result[0].Subscriber.SubscriberID) - }) + config := &Config{ + RegisteryURL: server.URL, + RetryMax: 1, + RetryWaitMin: 1 * time.Millisecond, + RetryWaitMax: 2 * time.Millisecond, } + rClient := NewRegisteryClient(config) + ctx := context.Background() + subscription := &model.Subscription{ + Subscriber: model.Subscriber{ + SubscriberID: "123", + }, + KeyID: "test-key", + SigningPublicKey: "test-signing-key", + EncrPublicKey: "test-encryption-key", + ValidFrom: time.Now(), + ValidUntil: time.Now().Add(24 * time.Hour), + Status: "SUBSCRIBED", + } + + result, err := rClient.Lookup(ctx, subscription) + require.NoError(t, err) + require.NotEmpty(t, result) + require.Equal(t, subscription.Subscriber.SubscriberID, result[0].Subscriber.SubscriberID) } // TestLookupFailure tests failure scenarios for the Lookup function. diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index 1a39d51..712fa2d 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -245,7 +245,7 @@ func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Conf case "validateSchema": s, err = newValidateSchemaStep(h.schemaValidator) case "addRoute": - s, err = newRouteStep(h.router) + s, err = newAddRouteStep(h.router) case "broadcast": s = &broadcastStep{} default: diff --git a/core/module/handler/step.go b/core/module/handler/step.go index 9074843..cd44b1a 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -41,7 +41,9 @@ func (s *signStep) Run(ctx *model.StepContext) error { if err != nil { return fmt.Errorf("failed to sign request: %w", err) } - authHeader := fmt.Sprintf("Signature keyId=\"%s|%s|ed25519\",algorithm=\"ed25519\",created=\"%d\",expires=\"%d\",headers=\"(created) (expires) digest\",signature=\"%s\"", ctx.SubID, keyID, createdAt, validTill, sign) + + authHeader := s.generateAuthHeader(ctx.SubID, keyID, createdAt, validTill, sign) + header := model.AuthHeaderSubscriber if ctx.Role == model.RoleGateway { header = model.AuthHeaderGateway @@ -50,6 +52,15 @@ func (s *signStep) Run(ctx *model.StepContext) error { return nil } +// generateAuthHeader constructs the authorization header for the signed request. +// It includes key ID, algorithm, creation time, expiration time, required headers, and signature. +func (s *signStep) generateAuthHeader(subID, keyID string, createdAt, validTill int64, signature string) string { + return fmt.Sprintf( + "Signature keyId=\"%s|%s|ed25519\",algorithm=\"ed25519\",created=\"%d\",expires=\"%d\",headers=\"(created) (expires) digest\",signature=\"%s\"", + subID, keyID, createdAt, validTill, signature, + ) +} + // validateSignStep represents the signature validation step. type validateSignStep struct { validator definition.SignValidator @@ -135,8 +146,8 @@ type addRouteStep struct { router definition.Router } -// newRouteStep creates and returns the addRoute step after validation. -func newRouteStep(router definition.Router) (definition.Step, error) { +// newAddRouteStep creates and returns the addRoute step after validation. +func newAddRouteStep(router definition.Router) (definition.Step, error) { if router == nil { return nil, fmt.Errorf("invalid config: Router plugin not configured") } @@ -149,13 +160,11 @@ func (s *addRouteStep) Run(ctx *model.StepContext) error { if err != nil { return fmt.Errorf("failed to determine route: %w", err) } - log.Debugf(ctx, "Routing to %#v", route) ctx.Route = &model.Route{ TargetType: route.TargetType, PublisherID: route.PublisherID, URL: route.URL, } - log.Debugf(ctx, "ctx.Route to %#v", ctx.Route) return nil } diff --git a/go.mod b/go.mod index 044bb06..3ec4911 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/beckn/beckn-onix -go 1.24.1 +go 1.24 require ( github.com/kr/pretty v0.3.1 // indirect diff --git a/pkg/model/model.go b/pkg/model/model.go index ec5e29c..621bdeb 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -81,17 +81,18 @@ func (r *Role) UnmarshalYAML(unmarshal func(interface{}) error) error { // Route represents a network route for message processing. type Route struct { - TargetType string // "url" or "msgq" or "bap" or "bpp" + TargetType string // "url" or "publisher" PublisherID string // For message queues URL *url.URL // For API calls } +// Keyset represents a collection of cryptographic keys used for signing and encryption. type Keyset struct { - UniqueKeyID string - SigningPrivate string - SigningPublic string - EncrPrivate string - EncrPublic string + UniqueKeyID string // UniqueKeyID is the identifier for the key pair. + SigningPrivate string // SigningPrivate is the private key used for signing operations. + SigningPublic string // SigningPublic is the public key corresponding to the signing private key. + EncrPrivate string // EncrPrivate is the private key used for encryption operations. + EncrPublic string // EncrPublic is the public key corresponding to the encryption private key. } // StepContext holds context information for a request processing step. diff --git a/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go b/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go index 020df4d..24ffa67 100644 --- a/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go +++ b/pkg/plugin/implementation/reqpreprocessor/reqpreprocessor.go @@ -13,9 +13,10 @@ import ( "github.com/google/uuid" ) +// Config holds the configuration settings for the application. type Config struct { - ContextKeys []string - Role string + ContextKeys []string // ContextKeys is a list of context keys used for request processing. + Role string // Role specifies the role of the entity (e.g., subscriber, gateway). } type becknRequest struct { @@ -25,6 +26,8 @@ type becknRequest struct { const contextKey = "context" const subscriberIDKey = "subscriber_id" +// NewPreProcessor creates a middleware that processes incoming HTTP requests by extracting +// and modifying the request context based on the provided configuration. func NewPreProcessor(cfg *Config) (func(http.Handler) http.Handler, error) { if err := validateConfig(cfg); err != nil { return nil, err @@ -51,6 +54,7 @@ func NewPreProcessor(cfg *Config) (func(http.Handler) http.Handler, error) { } if subID != nil { log.Debugf(ctx, "adding subscriberId to request:%s, %v", subscriberIDKey, subID) + // TODO: Add a ContextKey type in model and use it here instead of raw context key. ctx = context.WithValue(ctx, subscriberIDKey, subID) } for _, key := range cfg.ContextKeys { diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index ba36715..beaac8b 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -201,11 +201,6 @@ func validateRules(rules []routingRule) error { // Route determines the routing destination based on the request context. func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*model.Route, error) { - if r == nil { - - log.Debug(ctx, "In Router :Router not set") - } - log.Debugf(ctx, "In Router: Routing request with url %v and body: %s", url, string(body)) // Parse the body to extract domain and version var requestBody struct { Context struct { @@ -218,17 +213,11 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*model.R if err := json.Unmarshal(body, &requestBody); err != nil { return nil, fmt.Errorf("error parsing request body: %w", err) } - log.Debugf(ctx, "In Router: Routing request with %v and body: %#s", url, requestBody) + log.Debugf(ctx, "In Router: Routing request with %v and body: %v", url, requestBody) // Extract the endpoint from the URL endpoint := path.Base(url.Path) - if r.rules == nil { - - log.Debug(ctx, "In Router :Routing rules not set") - } - log.Debugf(ctx, "In Router :Routing rules len :%d", len(r.rules)) - // Lookup route in the optimized map domainRules, ok := r.rules[requestBody.Context.Domain] if !ok { diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 23b517e..bd969c4 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -17,15 +17,19 @@ import ( "github.com/beckn/beckn-onix/pkg/plugin/definition" ) +// TODO: Add unit tests for the plugin manager functions to ensure proper functionality and error handling. + +// Manager is responsible for managing dynamically loaded plugins. type Manager struct { - plugins map[string]*plugin.Plugin - closers []func() + plugins map[string]*plugin.Plugin // plugins holds the dynamically loaded plugins. + closers []func() // closers contains functions to release resources when the manager is closed. } func validateMgrCfg(cfg *ManagerConfig) error { return nil } +// NewManager initializes a new Manager instance by loading plugins from the specified configuration. func NewManager(ctx context.Context, cfg *ManagerConfig) (*Manager, func(), error) { if err := validateMgrCfg(cfg); err != nil { return nil, nil, fmt.Errorf("Invalid config: %w", err) @@ -64,14 +68,10 @@ func plugins(ctx context.Context, cfg *ManagerConfig) (map[string]*plugin.Plugin if strings.HasSuffix(d.Name(), ".so") { id := strings.TrimSuffix(d.Name(), ".so") // Extract plugin ID - - log.Debugf(ctx, "Loading plugin: %s", id) - start := time.Now() - p, err := plugin.Open(path) // Use the full path + p, elapsed, err := loadPlugin(ctx, path, id) if err != nil { - return fmt.Errorf("failed to open plugin %s: %w", id, err) + return err } - elapsed := time.Since(start) plugins[id] = p log.Debugf(ctx, "Loaded plugin: %s in %s", id, elapsed) } @@ -85,6 +85,20 @@ func plugins(ctx context.Context, cfg *ManagerConfig) (map[string]*plugin.Plugin return plugins, nil } +// loadPlugin attempts to load a plugin from the given path and logs the execution time. +func loadPlugin(ctx context.Context, path, id string) (*plugin.Plugin, time.Duration, error) { + log.Debugf(ctx, "Loading plugin: %s", id) + start := time.Now() + + p, err := plugin.Open(path) + if err != nil { + return nil, 0, fmt.Errorf("failed to open plugin %s: %w", id, err) + } + + elapsed := time.Since(start) + return p, elapsed, nil +} + func provider[T any](plugins map[string]*plugin.Plugin, id string) (T, error) { var zero T pgn, ok := plugins[id] @@ -105,8 +119,8 @@ func provider[T any](plugins map[string]*plugin.Plugin, id string) (T, error) { return pp, nil } -// GetPublisher returns a Publisher instance based on the provided configuration. -// It reuses the loaded provider. +// Publisher returns a Publisher instance based on the provided configuration. +// It reuses the loaded provider and registers a cleanup function. func (m *Manager) Publisher(ctx context.Context, cfg *Config) (definition.Publisher, error) { pp, err := provider[definition.PublisherProvider](m.plugins, cfg.ID) if err != nil { @@ -120,12 +134,15 @@ func (m *Manager) Publisher(ctx context.Context, cfg *Config) (definition.Publis return p, nil } +// addCloser appends a cleanup function to the Manager's closers list. func (m *Manager) addCloser(closer func()) { if closer != nil { m.closers = append(m.closers, closer) } } +// SchemaValidator returns a SchemaValidator instance based on the provided configuration. +// It registers a cleanup function for resource management. func (m *Manager) SchemaValidator(ctx context.Context, cfg *Config) (definition.SchemaValidator, error) { vp, err := provider[definition.SchemaValidatorProvider](m.plugins, cfg.ID) if err != nil { @@ -145,12 +162,17 @@ func (m *Manager) SchemaValidator(ctx context.Context, cfg *Config) (definition. return v, nil } +// Router returns a Router instance based on the provided configuration. +// It registers a cleanup function for resource management. func (m *Manager) Router(ctx context.Context, cfg *Config) (definition.Router, error) { rp, err := provider[definition.RouterProvider](m.plugins, cfg.ID) if err != nil { return nil, fmt.Errorf("failed to load provider for %s: %w", cfg.ID, err) } router, closer, err := rp.New(ctx, cfg.Config) + if err != nil { + return nil, err + } if closer != nil { m.addCloser(func() { if err := closer(); err != nil { @@ -161,6 +183,7 @@ func (m *Manager) Router(ctx context.Context, cfg *Config) (definition.Router, e return router, nil } +// Middleware returns an HTTP middleware function based on the provided configuration. func (m *Manager) Middleware(ctx context.Context, cfg *Config) (func(http.Handler) http.Handler, error) { mwp, err := provider[definition.MiddlewareProvider](m.plugins, cfg.ID) if err != nil { @@ -169,6 +192,7 @@ func (m *Manager) Middleware(ctx context.Context, cfg *Config) (func(http.Handle return mwp.New(ctx, cfg.Config) } +// Step returns a Step instance based on the provided configuration. func (m *Manager) Step(ctx context.Context, cfg *Config) (definition.Step, error) { sp, err := provider[definition.StepProvider](m.plugins, cfg.ID) if err != nil { @@ -181,6 +205,8 @@ func (m *Manager) Step(ctx context.Context, cfg *Config) (definition.Step, error return step, error } +// Cache returns a Cache instance based on the provided configuration. +// It registers a cleanup function for resource management. func (m *Manager) Cache(ctx context.Context, cfg *Config) (definition.Cache, error) { cp, err := provider[definition.CacheProvider](m.plugins, cfg.ID) if err != nil { @@ -198,6 +224,8 @@ func (m *Manager) Cache(ctx context.Context, cfg *Config) (definition.Cache, err return c, nil } +// Signer returns a Signer instance based on the provided configuration. +// It registers a cleanup function for resource management. func (m *Manager) Signer(ctx context.Context, cfg *Config) (definition.Signer, error) { sp, err := provider[definition.SignerProvider](m.plugins, cfg.ID) if err != nil { @@ -216,6 +244,9 @@ func (m *Manager) Signer(ctx context.Context, cfg *Config) (definition.Signer, e } return s, nil } + +// Encryptor returns an Encrypter instance based on the provided configuration. +// It registers a cleanup function for resource management. func (m *Manager) Encryptor(ctx context.Context, cfg *Config) (definition.Encrypter, error) { ep, err := provider[definition.EncrypterProvider](m.plugins, cfg.ID) if err != nil { @@ -235,6 +266,8 @@ func (m *Manager) Encryptor(ctx context.Context, cfg *Config) (definition.Encryp return encrypter, nil } +// Decryptor returns a Decrypter instance based on the provided configuration. +// It registers a cleanup function for resource management. func (m *Manager) Decryptor(ctx context.Context, cfg *Config) (definition.Decrypter, error) { dp, err := provider[definition.DecrypterProvider](m.plugins, cfg.ID) if err != nil { @@ -257,6 +290,8 @@ func (m *Manager) Decryptor(ctx context.Context, cfg *Config) (definition.Decryp return decrypter, nil } +// SignValidator returns a SignValidator instance based on the provided configuration. +// It registers a cleanup function for resource management. func (m *Manager) SignValidator(ctx context.Context, cfg *Config) (definition.SignValidator, error) { svp, err := provider[definition.SignValidatorProvider](m.plugins, cfg.ID) if err != nil { From 2fe39a2c0afa07ba27aea519c1d5a773af697069 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Mon, 31 Mar 2025 23:16:26 +0530 Subject: [PATCH 13/15] resolved linting issues --- core/module/client/registry_test.go | 16 ++++++++++++---- pkg/plugin/implementation/router/router.go | 2 -- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/module/client/registry_test.go b/core/module/client/registry_test.go index 5185597..608c328 100644 --- a/core/module/client/registry_test.go +++ b/core/module/client/registry_test.go @@ -27,7 +27,9 @@ func (m *MockRegistryClient) Subscribe(ctx context.Context, subscription *model. func TestSubscribeSuccess(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte("{}")) + if _, err := w.Write([]byte("{}")); err != nil { + t.Errorf("failed to write response: %v", err) + } })) defer server.Close() @@ -125,7 +127,9 @@ func TestLookupSuccess(t *testing.T) { }, } bodyBytes, _ := json.Marshal(response) - w.Write(bodyBytes) + if _, err := w.Write(bodyBytes); err != nil { + t.Errorf("failed to write response: %v", err) + } })) defer server.Close() @@ -194,10 +198,14 @@ func TestLookupFailure(t *testing.T) { } if tc.responseBody != nil { if str, ok := tc.responseBody.(string); ok { - w.Write([]byte(str)) + if _, err := w.Write([]byte(str)); err != nil { + t.Errorf("failed to write response: %v", err) + } } else { bodyBytes, _ := json.Marshal(tc.responseBody) - w.Write(bodyBytes) + if _, err := w.Write(bodyBytes); err != nil { + t.Errorf("failed to write response: %v", err) + } } } })) diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index beaac8b..1eb08ca 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -9,7 +9,6 @@ import ( "path" "strings" - "github.com/beckn/beckn-onix/pkg/log" "github.com/beckn/beckn-onix/pkg/model" "gopkg.in/yaml.v3" @@ -213,7 +212,6 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*model.R if err := json.Unmarshal(body, &requestBody); err != nil { return nil, fmt.Errorf("error parsing request body: %w", err) } - log.Debugf(ctx, "In Router: Routing request with %v and body: %v", url, requestBody) // Extract the endpoint from the URL endpoint := path.Base(url.Path) From 450f13cf346b9f8eb57b70be05de9aebbbe4d9da Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Tue, 1 Apr 2025 12:31:43 +0530 Subject: [PATCH 14/15] updated code as per the review comments --- core/module/handler/stdHandler.go | 2 - core/module/handler/step.go | 9 --- core/module/module_test.go | 54 +++++++--------- pkg/plugin/implementation/router/router.go | 72 ++++++++-------------- 4 files changed, 45 insertions(+), 92 deletions(-) diff --git a/core/module/handler/stdHandler.go b/core/module/handler/stdHandler.go index 712fa2d..251102e 100644 --- a/core/module/handler/stdHandler.go +++ b/core/module/handler/stdHandler.go @@ -246,8 +246,6 @@ func (h *stdHandler) initSteps(ctx context.Context, mgr PluginManager, cfg *Conf s, err = newValidateSchemaStep(h.schemaValidator) case "addRoute": s, err = newAddRouteStep(h.router) - case "broadcast": - s = &broadcastStep{} default: if customStep, exists := steps[step]; exists { s = customStep diff --git a/core/module/handler/step.go b/core/module/handler/step.go index cd44b1a..936ee98 100644 --- a/core/module/handler/step.go +++ b/core/module/handler/step.go @@ -167,12 +167,3 @@ func (s *addRouteStep) Run(ctx *model.StepContext) error { } return nil } - -// broadcastStep is a stub implementation of a step that handles broadcasting messages. -type broadcastStep struct{} - -// Run executes the broadcast step. -func (b *broadcastStep) Run(ctx *model.StepContext) error { - // TODO: Implement broadcast logic if needed - return nil -} diff --git a/core/module/module_test.go b/core/module/module_test.go index a4e1106..8f9016c 100644 --- a/core/module/module_test.go +++ b/core/module/module_test.go @@ -69,45 +69,33 @@ func (m *mockPluginManager) SchemaValidator(ctx context.Context, cfg *plugin.Con // TestRegisterSuccess tests scenarios where the handler registration should succeed. func TestRegisterSuccess(t *testing.T) { - tests := []struct { - name string - mCfgs []Config - mockManager *mockPluginManager - }{ + mCfgs := []Config{ { - name: "successful registration", - mCfgs: []Config{ - { - Name: "test-module", - Path: "/test", - Handler: handler.Config{ - Type: handler.HandlerTypeStd, - Plugins: handler.PluginCfg{ - Middleware: []plugin.Config{{ID: "mock-middleware"}}, - }, - }, - }, - }, - mockManager: &mockPluginManager{ - middlewareFunc: func(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - next.ServeHTTP(w, r) - }) - }, nil + Name: "test-module", + Path: "/test", + Handler: handler.Config{ + Type: handler.HandlerTypeStd, + Plugins: handler.PluginCfg{ + Middleware: []plugin.Config{{ID: "mock-middleware"}}, }, }, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mux := http.NewServeMux() - err := Register(context.Background(), tt.mCfgs, mux, tt.mockManager) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - }) + mockManager := &mockPluginManager{ + middlewareFunc: func(ctx context.Context, cfg *plugin.Config) (func(http.Handler) http.Handler, error) { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + }) + }, nil + }, + } + + mux := http.NewServeMux() + err := Register(context.Background(), mCfgs, mux, mockManager) + if err != nil { + t.Errorf("unexpected error: %v", err) } } diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index 1eb08ca..52e628b 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -71,30 +71,6 @@ func New(ctx context.Context, config *Config) (*Router, func() error, error) { return router, nil, nil } -// parseTargetURL parses a URL string into a url.URL object with strict validation -func parseTargetURL(urlStr string) (*url.URL, error) { - if urlStr == "" { - return nil, nil - } - - parsed, err := url.Parse(urlStr) - if err != nil { - return nil, fmt.Errorf("invalid URL '%s': %w", urlStr, err) - } - - // Enforce scheme requirement - if parsed.Scheme == "" { - return nil, fmt.Errorf("URL '%s' must include a scheme (http/https)", urlStr) - } - - // Optionally validate scheme is http or https - if parsed.Scheme != "https" { - return nil, fmt.Errorf("URL '%s' must use https scheme", urlStr) - } - - return parsed, nil -} - // LoadRules reads and parses routing rules from the YAML configuration file. func (r *Router) loadRules(configPath string) error { if configPath == "" { @@ -135,7 +111,7 @@ func (r *Router) loadRules(configPath string) error { PublisherID: rule.Target.PublisherID, } case targetTypeURL: - parsedURL, err := parseTargetURL(rule.Target.URL) + parsedURL, err := url.Parse(rule.Target.URL) if err != nil { return fmt.Errorf("invalid URL in rule: %w", err) } @@ -146,7 +122,7 @@ func (r *Router) loadRules(configPath string) error { case targetTypeBPP, targetTypeBAP: var parsedURL *url.URL if rule.Target.URL != "" { - parsedURL, err = parseTargetURL(rule.Target.URL) + parsedURL, err = url.Parse(rule.Target.URL) if err != nil { return fmt.Errorf("invalid URL in rule: %w", err) } @@ -177,7 +153,7 @@ func validateRules(rules []routingRule) error { if rule.Target.URL == "" { return fmt.Errorf("invalid rule: url is required for targetType 'url'") } - if _, err := parseTargetURL(rule.Target.URL); err != nil { + if _, err := url.Parse(rule.Target.URL); err != nil { return fmt.Errorf("invalid URL - %s: %w", rule.Target.URL, err) } case targetTypePublisher: @@ -186,7 +162,7 @@ func validateRules(rules []routingRule) error { } case targetTypeBPP, targetTypeBAP: if rule.Target.URL != "" { - if _, err := parseTargetURL(rule.Target.URL); err != nil { + if _, err := url.Parse(rule.Target.URL); err != nil { return fmt.Errorf("invalid URL - %s defined in routing config for target type %s: %w", rule.Target.URL, rule.TargetType, err) } } @@ -243,32 +219,32 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*model.R } // handleProtocolMapping handles both BPP and BAP routing with proper URL construction -func handleProtocolMapping(route *model.Route, requestURI, endpoint string) (*model.Route, error) { - uri := strings.TrimSpace(requestURI) - var targetURL *url.URL - if len(uri) != 0 { - parsedURL, err := parseTargetURL(uri) - if err != nil { - return nil, fmt.Errorf("invalid %s URI - %s in request body for %s: %w", strings.ToUpper(route.TargetType), uri, endpoint, err) - } - targetURL = parsedURL - } - - // If no request URI, fall back to configured URL with endpoint appended - if targetURL == nil { +func handleProtocolMapping(route *model.Route, npURI, endpoint string) (*model.Route, error) { + target := strings.TrimSpace(npURI) + if len(target) == 0 { if route.URL == nil { return nil, fmt.Errorf("could not determine destination for endpoint '%s': neither request contained a %s URI nor was a default URL configured in routing rules", endpoint, strings.ToUpper(route.TargetType)) } - - targetURL = &url.URL{ - Scheme: route.URL.Scheme, - Host: route.URL.Host, - Path: path.Join(route.URL.Path, endpoint), - } + return &model.Route{ + TargetType: targetTypeURL, + URL: &url.URL{ + Scheme: route.URL.Scheme, + Host: route.URL.Host, + Path: path.Join(route.URL.Path, endpoint), + }, + }, nil + } + targetURL, err := url.Parse(target) + if err != nil { + return nil, fmt.Errorf("invalid %s URI - %s in request body for %s: %w", strings.ToUpper(route.TargetType), target, endpoint, err) } return &model.Route{ TargetType: targetTypeURL, - URL: targetURL, + URL: &url.URL{ + Scheme: targetURL.Scheme, + Host: targetURL.Host, + Path: path.Join(targetURL.Path, endpoint), + }, }, nil } From b879cb8faa0876c8e567f9d55263b10732cca388 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean Date: Tue, 1 Apr 2025 15:26:38 +0530 Subject: [PATCH 15/15] updated code as per the review comment 1. changed struct to httpConfig 2. Removed initLogger func var 3. Removed mocked registryClient from registry test code --- cmd/adapter/main.go | 7 +++---- cmd/adapter/main_test.go | 10 +++++----- core/module/client/registry_test.go | 24 +++++++----------------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index a581a59..1c88c60 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -25,10 +25,10 @@ type Config struct { Log log.Config `yaml:"log"` PluginManager *plugin.ManagerConfig `yaml:"pluginManager"` Modules []module.Config `yaml:"modules"` - HTTP timeouts `yaml:"http"` + HTTP httpConfig `yaml:"http"` } -type timeouts struct { +type httpConfig struct { Port string `yaml:"port"` Timeouts timeoutConfig `yaml:"timeout"` } @@ -103,7 +103,6 @@ func newServer(ctx context.Context, mgr handler.PluginManager, cfg *Config) (htt var newManagerFunc = plugin.NewManager var newServerFunc = newServer -var initLoggerFunc = log.InitLogger // run encapsulates the application logic. func run(ctx context.Context, configPath string) error { @@ -114,7 +113,7 @@ func run(ctx context.Context, configPath string) error { return fmt.Errorf("failed to initialize config: %w", err) } log.Infof(ctx, "Initializing logger with config: %+v", cfg.Log) - if err := initLoggerFunc(cfg.Log); err != nil { + if err := log.InitLogger(cfg.Log); err != nil { return fmt.Errorf("failed to initialize logger: %w", err) } diff --git a/cmd/adapter/main_test.go b/cmd/adapter/main_test.go index b1b15d1..8bfd24e 100644 --- a/cmd/adapter/main_test.go +++ b/cmd/adapter/main_test.go @@ -364,7 +364,7 @@ func TestNewServerSuccess(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := &Config{ Modules: tt.modules, - HTTP: timeouts{ + HTTP: httpConfig{ Port: "8080", Timeouts: timeoutConfig{ Read: 5, @@ -409,7 +409,7 @@ func TestNewServerFailure(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := &Config{ Modules: tt.modules, - HTTP: timeouts{ + HTTP: httpConfig{ Port: "8080", Timeouts: timeoutConfig{ Read: 5, @@ -441,7 +441,7 @@ func TestValidateConfigSuccess(t *testing.T) { name: "Valid Config", cfg: Config{ AppName: "TestApp", - HTTP: timeouts{ + HTTP: httpConfig{ Port: "8080", }, }, @@ -469,7 +469,7 @@ func TestValidateConfigFailure(t *testing.T) { name: "Missing AppName", cfg: Config{ AppName: "", - HTTP: timeouts{ + HTTP: httpConfig{ Port: "8080", }, }, @@ -479,7 +479,7 @@ func TestValidateConfigFailure(t *testing.T) { name: "Missing Port", cfg: Config{ AppName: "TestApp", - HTTP: timeouts{ + HTTP: httpConfig{ Port: "", }, }, diff --git a/core/module/client/registry_test.go b/core/module/client/registry_test.go index 608c328..ac05c14 100644 --- a/core/module/client/registry_test.go +++ b/core/module/client/registry_test.go @@ -13,16 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -// MockRegistryClient is a mock implementation of the RegistryClient. -type MockRegistryClient struct { - SubscribeFunc func(ctx context.Context, subscription *model.Subscription) error -} - -// Subscribe calls the mock Subscribe function. -func (m *MockRegistryClient) Subscribe(ctx context.Context, subscription *model.Subscription) error { - return m.SubscribeFunc(ctx, subscription) -} - // TestSubscribeSuccess verifies that the Subscribe function succeeds when the server responds with HTTP 200. func TestSubscribeSuccess(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -84,11 +74,11 @@ func TestSubscribeFailure(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockClient := &MockRegistryClient{ - SubscribeFunc: func(ctx context.Context, subscription *model.Subscription) error { - return tt.mockError - }, - } + client := NewRegisteryClient(&Config{ + RetryMax: 1, + RetryWaitMin: 1 * time.Millisecond, + RetryWaitMax: 2 * time.Millisecond, + }) subscription := &model.Subscription{ KeyID: "test-key", @@ -103,7 +93,7 @@ func TestSubscribeFailure(t *testing.T) { subscription = &model.Subscription{} // Example of an invalid object } - err := mockClient.Subscribe(context.Background(), subscription) + err := client.Subscribe(context.Background(), subscription) require.Error(t, err) // Directly checking for an error since all cases should fail }) } @@ -217,7 +207,7 @@ func TestLookupFailure(t *testing.T) { config := &Config{ RegisteryURL: server.URL, - RetryMax: 0, // Prevent excessive retries + RetryMax: 0, RetryWaitMin: 1 * time.Millisecond, RetryWaitMax: 2 * time.Millisecond, }