added updated code for core wiring

1. Removed tracing
2. Skipped Registration
This commit is contained in:
MohitKatare-protean
2025-03-25 21:06:34 +05:30
parent 519cca19af
commit ec558558c5
87 changed files with 9279 additions and 711 deletions

175
cmd/adapter/main.go Normal file
View File

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

69
config/bap.yaml Normal file
View File

@@ -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

View File

@@ -0,0 +1,3 @@
routes:
- action: search
target: https://bpp-adapter-903496459467.asia-southeast1.run.app/reciever

63
config/bpp-local.yaml Normal file
View File

@@ -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"

63
config/bpp.yaml Normal file
View File

@@ -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

View File

@@ -0,0 +1,3 @@
routes:
- action: on_search
target: targeturl

View File

@@ -0,0 +1,3 @@
routes:
- action: search
target: https://sellerapp-903496459467.asia-southeast1.run.app

View File

@@ -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

View File

@@ -0,0 +1,4 @@
routes:
- action: search
type: url
target: http://localhost:8080/bpp/reciever/search

View File

@@ -0,0 +1,4 @@
routes:
- action: on_search
type: publisher
target: bapNetworkReciever

View File

@@ -0,0 +1,4 @@
routes:
- action: on_search
type: url
target: http://localhost:8080/bap/reciever/on_search

View File

@@ -0,0 +1,4 @@
routes:
- action: search
type: publisher
target: bapNetworkReciever

View File

@@ -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"

8
config/onix/plugin.yaml Normal file
View File

@@ -0,0 +1,8 @@
plugins:
- gcpAuthMdw
- nopsigner
- router
- publisher
- reqpreprocessor
- nopschemavalidator
- nopsignvalidator

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "Response",
"type": "object",
"properties": {},
"required": []
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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"]
}
}
}
]
}

View File

@@ -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"
]
}
}
}
}
}
}
]
}

View File

@@ -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"
]
}
}
}
}
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"]
}
]
}
]
}

View File

@@ -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"]
}
]
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
]
}
}
}
}
}
}
}
}
}
}
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"]
}

View File

@@ -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"
}
]
}

View File

@@ -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"]
}
}
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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"
}
]
}

View File

@@ -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": "^[^,]+(,[^,]+)*$"
}
}
}
}
}
]
}

69
config/sellerData.yaml Normal file
View File

@@ -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

View File

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

View File

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

View File

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

181
core/module/handler/step.go Normal file
View File

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

84
core/module/module.go Normal file
View File

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

49
go.mod
View File

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

177
go.sum
View File

@@ -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=

322
pkg/log/log.go Normal file
View File

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

128
pkg/model/error.go Normal file
View File

@@ -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(),
}
}

132
pkg/model/model.go Normal file
View File

@@ -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"`
}

21
pkg/plugin/config.go Normal file
View File

@@ -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"`
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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, &notFoundErr):
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),
}
}

View File

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