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) -}