merged with latest main

This commit is contained in:
Manendra Pal Singh
2025-11-28 11:06:14 +05:30
31 changed files with 952 additions and 78 deletions

130
CONFIG.md
View File

@@ -481,7 +481,34 @@ registry:
--- ---
#### 2. Key Manager Plugin #### 2. Dediregistry Plugin
**Purpose**: Lookup participant information from a Decentralized Discovery (DeDi) registry.
**Configuration**:
```yaml
registry:
id: dediregistry
config:
url: "https://dedi-wrapper.example.com/dedi"
registryName: "subscribers.beckn.one"
timeout: 30
retry_max: 3
retry_wait_min: 1s
retry_wait_max: 5s
```
**Parameters**:
- `url`: DeDi wrapper API base URL (Required)
- `registryName`: Name of the registry (Required)
- `timeout`: Request timeout in seconds (Optional, default: client default)
- `retry_max`: Maximum number of retry attempts (Optional, default: 4)
- `retry_wait_min`: Minimum wait time between retries in duration format (Optional, default: 1s)
- `retry_wait_max`: Maximum wait time between retries in duration format (Optional, default: 30s)
---
#### 3. Key Manager Plugin
**Purpose**: Manage cryptographic keys for signing and verification. **Purpose**: Manage cryptographic keys for signing and verification.
@@ -711,9 +738,43 @@ routingRules:
#### `domain` #### `domain`
**Type**: `string` **Type**: `string`
**Required**: Yes **Required**: Conditional (Required for v1.x.x, Optional for v2.x.x)
**Description**: Beckn domain identifier (e.g., `retail:1.1.0`, `ONDC:TRV10`, `nic2004:60221`) **Description**: Beckn domain identifier (e.g., `retail:1.1.0`, `ONDC:TRV10`, `nic2004:60221`)
**Version-Specific Behavior**:
- **Beckn Protocol v1.x.x**: Domain is **required**. Each rule must specify a domain, and routing uses domain as a key.
- **Beckn Protocol v2.x.x**: Domain is **optional** and ignored during routing. If provided, a warning is logged. All v2 rules are domain-agnostic.
- **Conflict Detection**: For v2, multiple rules with the same version and endpoint (regardless of domain) will cause a configuration error.
**Examples**:
```yaml
# Valid v1 rule - domain required
- domain: "ONDC:TRV10"
version: "1.1.0"
targetType: "url"
target:
url: "http://backend:3000"
endpoints:
- search
# Valid v2 rule - domain optional (omitted)
- version: "2.0.0"
targetType: "url"
target:
url: "http://backend:3000"
endpoints:
- search
# Valid v2 rule - domain provided (warning logged, but ignored)
- domain: "ONDC:TRV10"
version: "2.0.0"
targetType: "url"
target:
url: "http://backend:3000"
endpoints:
- search
```
#### `version` #### `version`
**Type**: `string` **Type**: `string`
**Required**: Yes **Required**: Yes
@@ -881,6 +942,71 @@ routingRules:
**Behavior**: All endpoints route to exactly `http://backend:3000/webhook` without appending the endpoint name. **Behavior**: All endpoints route to exactly `http://backend:3000/webhook` without appending the endpoint name.
#### Example 5: Beckn Protocol v2 Domain-Agnostic Routing
```yaml
routingRules:
# v2 rule without domain (recommended)
- version: "2.0.0"
targetType: "url"
target:
url: "https://gateway.example.com/v2"
endpoints:
- search
- select
- init
- confirm
# v1 rules still require domain
- domain: "ONDC:TRV10"
version: "1.1.0"
targetType: "url"
target:
url: "https://gateway.example.com/v1/trv"
endpoints:
- search
```
**Behavior**:
- v2 requests (version `2.0.0`) route to gateway regardless of domain in request
- v1 requests (version `1.1.0`) route based on domain matching
- Domain field is ignored for v2 routing decisions
#### Example 6: v2 Conflict Detection
```yaml
# INVALID CONFIGURATION - Will fail at startup
routingRules:
- domain: "ONDC:TRV10"
version: "2.0.0"
targetType: "url"
target:
url: "https://backend-a.com"
endpoints:
- search
- domain: "ONDC:TRV11" # Different domain, but same version and endpoint
version: "2.0.0"
targetType: "url"
target:
url: "https://backend-b.com"
endpoints:
- search # ERROR: Duplicate v2 rule for 'search' endpoint
```
**Error**: Configuration will fail with: `duplicate endpoint 'search' found for version 2.0.0. For v2.x.x, domain is ignored, so you can only define each endpoint once per version. Please remove the duplicate rule`
**Fix**: For v2, use a single rule per endpoint since domain is ignored:
```yaml
routingRules:
- version: "2.0.0"
targetType: "url"
target:
url: "https://unified-backend.com"
endpoints:
- search
```
--- ---
## Deployment Scenarios ## Deployment Scenarios

View File

@@ -151,7 +151,7 @@ Resources:
#### 3. **Plugin Types** #### 3. **Plugin Types**
- **Cache**: Redis-based response caching - **Cache**: Redis-based response caching
- **Router**: YAML-based routing rules engine for request forwarding - **Router**: YAML-based routing rules engine for request forwarding (supports domain-agnostic routing for Beckn v2.x.x)
- **Signer**: Ed25519 digital signature creation for outgoing requests - **Signer**: Ed25519 digital signature creation for outgoing requests
- **SignValidator**: Ed25519 signature validation for incoming requests - **SignValidator**: Ed25519 signature validation for incoming requests
- **SchemaValidator**: JSON schema validation - **SchemaValidator**: JSON schema validation
@@ -239,12 +239,18 @@ This automated script will:
- Start ONIX adapter in Docker - Start ONIX adapter in Docker
- Create environment configuration - Create environment configuration
**Note:** Extract schemas before running: `unzip schemas.zip` (required for schema validation) and before running the automated setup, build the adapter image if required ,update docker-compose-adapter.yaml to use the correct onix image **Note:**
- **Schema Validation**: Extract schemas before running: `unzip schemas.zip` (required for `schemavalidator` plugin)
- **Alternative**: You can use `schemav2validator` plugin instead, which fetches schemas from a URL and doesn't require local schema extraction. See [CONFIG.md](CONFIG.md) for more configuration details.
- **Optional**: Before running the automated setup, build the adapter image and update `docker-compose-adapter.yaml` to use the correct image
```bash ```bash
# from the repository root # from the repository root
docker build -f Dockerfile.adapter-with-plugins -t beckn-onix:latest . docker build -f Dockerfile.adapter-with-plugins -t beckn-onix:latest .
``` ```
**For detailed setup instructions, see [SETUP.md](SETUP.md)**
**Services Started:** **Services Started:**
- Redis: localhost:6379 - Redis: localhost:6379
- ONIX Adapter: http://localhost:8081 - ONIX Adapter: http://localhost:8081

View File

@@ -88,14 +88,53 @@ This will automatically:
**Key Management:** Uses `simplekeymanager` with embedded keys - no Vault setup required! **Key Management:** Uses `simplekeymanager` with embedded keys - no Vault setup required!
**Note:** Extract schemas before running: `unzip schemas.zip` (required for schema validation) and before running the automated setup, build the adapter image ,update docker-compose-adapter.yaml to use the correct image (optional) **Note:**
- **Schema Validation**: Extract schemas before running: `unzip schemas.zip` (required for `schemavalidator` plugin)
- **Alternative**: You can use `schemav2validator` plugin instead, which fetches schemas from a URL and doesn't require local schema extraction. See [CONFIG.md](CONFIG.md) for more configuration details.
- **Optional**: Before running the automated setup, build the adapter image and update `docker-compose-adapter.yaml` to use the correct image
```bash ```bash
# from the repository root # from the repository root
docker build -f Dockerfile.adapter-with-plugins -t beckn-onix:latest . docker build -f Dockerfile.adapter-with-plugins -t beckn-onix:latest .
``` ```
### Option 2: Complete Beckn Network ### Option 2: Beckn One Network Setup
For a local setup using Beckn One with DeDI-Registry and Catalog Discovery:
```bash
cd beckn-onix/install
chmod +x beckn-onix.sh
./beckn-onix.sh
# Choose option 3: "Set up a network on local machine with Beckn One (DeDI-Registry, Catalog Discovery)"
```
This will automatically:
- Install required packages (Docker, docker-compose, jq)
- Build ONIX adapter plugins
- Start Redis
- Start ONIX adapters for BAP and BPP with Beckn One configuration
- Start Sandbox containers for BAP and BPP
**Services Started:**
- Redis: localhost:6379
- ONIX Adapter (BAP): http://localhost:8081
- ONIX Adapter (BPP): http://localhost:8082
- Sandbox BAP: http://localhost:3001
- Sandbox BPP: http://localhost:3002
**Key Features:**
- Uses Beckn One (DeDI-Registry) for registry services
- Pre-configured keys in YAML configuration files
- No local registry or gateway deployment required
**Note:**
- **Schema Validation**: Extract schemas before running: `unzip schemas.zip` (required for `schemavalidator` plugin)
- **Alternative**: You can use `schemav2validator` plugin instead, which fetches schemas from a URL and doesn't require local schema extraction. See [CONFIG.md](CONFIG.md) for more configuration details.
### Option 3: Complete Beckn Network
For a full local Beckn network with all components: For a full local Beckn network with all components:
@@ -104,7 +143,8 @@ cd beckn-onix/install
chmod +x beckn-onix.sh chmod +x beckn-onix.sh
./beckn-onix.sh ./beckn-onix.sh
# Choose option 3: "Set up a network on your local machine" # Choose option 4: "Set up a network on local machine with local registry and gateway (without Beckn One)"
``` ```
This will automatically: This will automatically:
@@ -125,7 +165,10 @@ This will automatically:
- ONIX Adapter: http://localhost:8081 - ONIX Adapter: http://localhost:8081
- Redis: localhost:6379 - Redis: localhost:6379
**Note:** Extract schemas before running: `unzip schemas.zip` (required for schema validation) and before running the automated full-network setup, build the adapter image , update docker-compose-adapter.yaml to use the correct image(optional) **Note:**
- **Schema Validation**: Extract schemas before running: `unzip schemas.zip` (required for `schemavalidator` plugin)
- **Alternative**: You can use `schemav2validator` plugin instead, which fetches schemas from a URL and doesn't require local schema extraction. See [CONFIG.md](CONFIG.md) for more configuration details.
- **Optional**: Before running the automated full-network setup, build the adapter image and update `docker-compose-adapter.yaml` to use the correct image
```bash ```bash
# from the repository root # from the repository root
@@ -193,6 +236,7 @@ This creates `.so` files in the `plugins/` directory:
- `signer.so` - Message signing - `signer.so` - Message signing
- `signvalidator.so` - Signature validation - `signvalidator.so` - Signature validation
- `schemavalidator.so` - JSON schema validation - `schemavalidator.so` - JSON schema validation
- `schemav2validator.so` - OpenAPI 3.x schema validation
- `keymanager.so` - Vault integration - `keymanager.so` - Vault integration
- `simplekeymanager.so` - Simple key management (no Vault) - `simplekeymanager.so` - Simple key management (no Vault)
- `publisher.so` - RabbitMQ publishing - `publisher.so` - RabbitMQ publishing
@@ -865,29 +909,39 @@ router:
### Routing Rules Configuration ### Routing Rules Configuration
**For complete routing configuration documentation including v2 domain-agnostic routing, see [CONFIG.md - Routing Configuration](CONFIG.md#routing-configuration).**
Basic example:
```yaml ```yaml
routingRules: routingRules:
# v1.x.x - domain is required
- domain: "ONDC:RET10" - domain: "ONDC:RET10"
version: "1.0.0" version: "1.0.0"
targetType: "url" # or "publisher" targetType: "url" # or "publisher"
target: target:
url: "https://seller.example.com/beckn" url: "https://seller.example.com/beckn"
# OR for async
# queueName: "retail_queue"
# routingKey: "retail.*"
endpoints: endpoints:
- search - search
- select - select
- init - init
- confirm - confirm
headers: # Optional additional headers
X-Custom-Header: "value" # v2.x.x - domain is optional (domain-agnostic routing)
timeout: 60 # seconds - version: "2.0.0"
retryPolicy: targetType: "url"
maxRetries: 3 target:
backoff: exponential url: "https://seller.example.com/v2/beckn"
endpoints:
- search
- select
``` ```
**Key Points**:
- **v1.x.x**: Domain field is required and used for routing
- **v2.x.x**: Domain field is optional and ignored (domain-agnostic)
- See CONFIG.md for target types: `url`, `bpp`, `bap`, `publisher`
### Processing Steps ### Processing Steps
Available steps for configuration: Available steps for configuration:

View File

@@ -0,0 +1,119 @@
appName: "onix-local"
log:
level: debug
destinations:
- type: stdout
contextKeys:
- transaction_id
- message_id
- subscriber_id
- module_id
http:
port: 8081
timeout:
read: 30
write: 30
idle: 30
pluginManager:
root: ./plugins
modules:
- name: bapTxnReceiver
path: /bap/receiver/
handler:
type: std
role: bap
httpClientConfig:
maxIdleConns: 1000
maxIdleConnsPerHost: 200
idleConnTimeout: 300s
responseHeaderTimeout: 5s
plugins:
registry:
id: dediregistry
config:
url: http://34.14.173.68:8080/dedi
registryName: subscribers.beckn.one
timeout: 10
retry_max: 3
retry_wait_min: 100ms
retry_wait_max: 500ms
keyManager:
id: simplekeymanager
config:
networkParticipant: ev-charging.sandbox1.com
keyId: 76EU7PktCXdPoNEZBjmg4Eb25A2egsd5MYJ67Qxza7bJQFvBHCYxgk
signingPrivateKey: 9NBh67Pk/6v3irrkYZHlQ5E1qw+GivHdDFtKeCylzIM=
signingPublicKey: Z3Hnc8FZDo/7dwWApeRVs6OV560gr7uxPsFUDGUMsBg=
encrPrivateKey: 9NBh67Pk/6v3irrkYZHlQ5E1qw+GivHdDFtKeCylzIM=
encrPublicKey: Z3Hnc8FZDo/7dwWApeRVs6OV560gr7uxPsFUDGUMsBg=
cache:
id: cache
config:
addr: redis:6379
schemaValidator:
id: schemavalidator
config:
schemaDir: ./schemas
signValidator:
id: signvalidator
router:
id: router
config:
routingConfig: ./config/local-beckn-one-routing-BAPReceiver.yaml
middleware:
- id: reqpreprocessor
config:
uuidKeys: transaction_id,message_id
role: bap
steps:
- validateSign
- addRoute
- name: bapTxnCaller
path: /bap/caller/
handler:
type: std
role: bap
httpClientConfig:
maxIdleConns: 1000
maxIdleConnsPerHost: 200
idleConnTimeout: 300s
responseHeaderTimeout: 5s
plugins:
registry:
id: dediregistry
config:
url: http://34.14.173.68:8080/dedi
registryName: subscribers.beckn.one
timeout: 10
retry_max: 3
retry_wait_min: 100ms
retry_wait_max: 500ms
keyManager:
id: simplekeymanager
config:
networkParticipant: ev-charging.sandbox1.com
keyId: 76EU7PktCXdPoNEZBjmg4Eb25A2egsd5MYJ67Qxza7bJQFvBHCYxgk
signingPrivateKey: 9NBh67Pk/6v3irrkYZHlQ5E1qw+GivHdDFtKeCylzIM=
signingPublicKey: Z3Hnc8FZDo/7dwWApeRVs6OV560gr7uxPsFUDGUMsBg=
encrPrivateKey: 9NBh67Pk/6v3irrkYZHlQ5E1qw+GivHdDFtKeCylzIM=
encrPublicKey: Z3Hnc8FZDo/7dwWApeRVs6OV560gr7uxPsFUDGUMsBg=
cache:
id: cache
config:
addr: redis:6379
router:
id: router
config:
routingConfig: ./config/local-beckn-one-routing-BAPCaller.yaml
signer:
id: signer
middleware:
- id: reqpreprocessor
config:
uuidKeys: transaction_id,message_id
role: bap
steps:
- addRoute
- sign

View File

@@ -0,0 +1,113 @@
appName: "onix-local"
log:
level: debug
destinations:
- type: stdout
contextKeys:
- transaction_id
- message_id
- subscriber_id
- module_id
http:
port: 8082
timeout:
read: 30
write: 30
idle: 30
pluginManager:
root: ./plugins
modules:
- name: bppTxnReceiver
path: /bpp/receiver/
handler:
type: std
role: bpp
httpClientConfig:
maxIdleConns: 1000
maxIdleConnsPerHost: 200
idleConnTimeout: 300s
responseHeaderTimeout: 5s
plugins:
registry:
id: dediregistry
config:
url: http://34.14.173.68:8080/dedi
registryName: subscribers.beckn.one
timeout: 10
retry_max: 3
retry_wait_min: 100ms
retry_wait_max: 500ms
keyManager:
id: simplekeymanager
config:
networkParticipant: ev-charging.sandbox2.com
keyId: 76EU7ncBX74BMNTQJMcMYoTMSzU7k71owUF53fN4jdxmosxZrdjdDk
signingPrivateKey: hnMdzvcZBnLJ6W1f4Y2ZCLlJo4phKMvs48ZbXjbS7/k=
signingPublicKey: H1xM/ejGIJpH+DmAF1A9KjBBLJ74pZ8B0gnZ4z4DIkU=
encrPrivateKey: hnMdzvcZBnLJ6W1f4Y2ZCLlJo4phKMvs48ZbXjbS7/k=
encrPublicKey: H1xM/ejGIJpH+DmAF1A9KjBBLJ74pZ8B0gnZ4z4DIkU=
cache:
id: cache
config:
addr: redis:6379
schemaValidator:
id: schemavalidator
config:
schemaDir: ./schemas
signValidator:
id: signvalidator
router:
id: router
config:
routingConfig: ./config/local-beckn-one-routing-BPPReceiver.yaml
steps:
- validateSign
- addRoute
- name: bppTxnCaller
path: /bpp/caller/
handler:
type: std
role: bpp
httpClientConfig:
maxIdleConns: 1000
maxIdleConnsPerHost: 200
idleConnTimeout: 300s
responseHeaderTimeout: 5s
plugins:
registry:
id: dediregistry
config:
url: http://34.14.173.68:8080/dedi
registryName: subscribers.beckn.one
timeout: 10
retry_max: 3
retry_wait_min: 100ms
retry_wait_max: 500ms
keyManager:
id: simplekeymanager
config:
networkParticipant: ev-charging.sandbox2.com
keyId: 76EU7ncBX74BMNTQJMcMYoTMSzU7k71owUF53fN4jdxmosxZrdjdDk
signingPrivateKey: hnMdzvcZBnLJ6W1f4Y2ZCLlJo4phKMvs48ZbXjbS7/k=
signingPublicKey: H1xM/ejGIJpH+DmAF1A9KjBBLJ74pZ8B0gnZ4z4DIkU=
encrPrivateKey: hnMdzvcZBnLJ6W1f4Y2ZCLlJo4phKMvs48ZbXjbS7/k=
encrPublicKey: H1xM/ejGIJpH+DmAF1A9KjBBLJ74pZ8B0gnZ4z4DIkU=
cache:
id: cache
config:
addr: redis:6379
router:
id: router
config:
routingConfig: ./config/local-beckn-one-routing-BPPCaller.yaml
signer:
id: signer
middleware:
- id: reqpreprocessor
config:
uuidKeys: transaction_id,message_id
role: bpp
steps:
- addRoute
- sign

View File

@@ -0,0 +1,22 @@
routingRules:
- domain: "beckn.one:deg:ev-charging:2.0.0" # Retail domain
version: "2.0.0"
targetType: "bpp"
endpoints:
- select
- init
- confirm
- status
- track
- cancel
- update
- rating
- support
- domain: "beckn.one:deg:ev-charging:2.0.0" # Retail domain
version: "2.0.0"
targetType: "url"
target:
url: "https://34.93.141.21.sslip.io/beckn"
endpoints:
- discover

View File

@@ -0,0 +1,14 @@
routingRules:
- domain: "beckn.one:deg:ev-charging:2.0.0"
version: "2.0.0"
targetType: "url"
target:
url: "http://sandbox-bap:3001/api/bap-webhook"
endpoints:
- on_discover
- on_select
- on_init
- on_confirm
- on_status
- on_update
- on_cancel

View File

@@ -0,0 +1,14 @@
routingRules:
- domain: "beckn.one:deg:ev-charging:2.0.0" # Retail domain
version: "2.0.0"
targetType: "bap"
endpoints:
- on_status
- on_cancel
- on_update
- on_select
- on_init
- on_confirm
- on_track
- on_rating
- on_support

View File

@@ -0,0 +1,16 @@
routingRules:
- domain: "beckn.one:deg:ev-charging:2.0.0" # Retail domain
version: "2.0.0"
targetType: "url"
target:
url: "http://sandbox-bpp:3002/api/webhook"
endpoints:
- select
- init
- confirm
- status
- track
- cancel
- update
- rating
- support

View File

@@ -928,6 +928,58 @@ install_bap_adapter() {
install_bpp_adapter() { install_bpp_adapter() {
install_adapter "BPP" install_adapter "BPP"
} }
# Function to install Beckn One Adapter (Option 3)
install_beckn_one_adapter() {
echo "${GREEN}................Setting up network with Beckn One................${NC}"
# Create schemas directory if not exists
if [ ! -d "schemas" ]; then
mkdir -p schemas
echo -e "${GREEN}✓ Created schemas directory${NC}"
fi
echo "${GREEN}................Building plugins for ONIX Adapter................${NC}"
# Build plugins
cd ..
if [ -f "./install/build-plugins.sh" ]; then
chmod +x ./install/build-plugins.sh
./install/build-plugins.sh
if [ $? -eq 0 ]; then
echo "${GREEN}✓ Plugins built successfully${NC}"
else
echo "${RED}Error: Plugin build failed${NC}"
exit 1
fi
else
echo "${RED}Error: install/build-plugins.sh not found${NC}"
exit 1
fi
cd install
echo "${GREEN}................Starting Redis and Beckn One Adapters................${NC}"
# Start Redis first
start_support_services
# Start Beckn One adapters (onix-bap, onix-bpp, sandbox-bap, sandbox-bpp)
start_container $adapter_beckn_one_docker_compose_file "onix-bap"
start_container $adapter_beckn_one_docker_compose_file "onix-bpp"
start_container $adapter_beckn_one_docker_compose_file "sandbox-bap"
start_container $adapter_beckn_one_docker_compose_file "sandbox-bpp"
sleep 10
echo "${GREEN}✓ Beckn One network setup complete${NC}"
echo ""
echo "${BLUE}Services running:${NC}"
echo " - ONIX BAP Adapter: http://localhost:8081"
echo " - ONIX BPP Adapter: http://localhost:8082"
echo " - Sandbox BAP: http://localhost:3001"
echo " - Sandbox BPP: http://localhost:3002"
echo " - Redis: localhost:6379"
}
# MAIN SCRIPT STARTS HERE # MAIN SCRIPT STARTS HERE
echo "Welcome to Beckn-ONIX!" echo "Welcome to Beckn-ONIX!"
@@ -941,29 +993,35 @@ echo "Checking prerequisites of Beckn-ONIX deployment"
check_docker_permissions check_docker_permissions
echo "Beckn-ONIX is a platform that helps you quickly launch and configure beckn-enabled networks." echo "Beckn-ONIX is a platform that helps you quickly launch and configure beckn-enabled networks."
echo -e "\nWhat would you like to do?\n1. Join an existing network\n2. Create new production network\n3. Set up a network on your local machine\n4. Merge multiple networks\n5. Configure Existing Network\n6. Update/Upgrade Application\n(Press Ctrl+C to exit)" echo -e "\nWhat would you like to do?\n1. Join an existing network\n2. Create new production network\n3. Set up a network on local machine with Beckn One (DeDI-Registry, Catalog Discovery)\n4. Set up a network on local machine with local registry and gateway (without Beckn One)\n5. Merge multiple networks\n6. Configure Existing Network\n7. Update/Upgrade Application\n(Press Ctrl+C to exit)"
read -p "Enter your choice: " choice read -p "Enter your choice: " choice
validate_input "$choice" 6 validate_input "$choice" 7
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
restart_script # Restart the script if input is invalid restart_script # Restart the script if input is invalid
fi fi
if [[ $choice -eq 3 ]]; then if [[ $choice -eq 3 ]]; then
echo "Installing all components on the local machine" echo "${GREEN}Setting up network with Beckn One${NC}"
echo "${BLUE}This setup uses Beckn One (DeDI-Registry) for registry and catalog discovery${NC}"
echo ""
install_package
install_beckn_one_adapter
elif [[ $choice -eq 4 ]]; then
echo "Installing network with local registry and gateway (without Beckn One)"
install_package install_package
install_registry install_registry
install_gateway install_gateway
install_bap_protocol_server install_bap_protocol_server
install_bpp_protocol_server_with_sandbox install_bpp_protocol_server_with_sandbox
install_adapter "BOTH" install_adapter "BOTH"
elif [[ $choice -eq 4 ]]; then elif [[ $choice -eq 5 ]]; then
echo "Determining the platforms available based on the initial choice" echo "Determining the platforms available based on the initial choice"
mergingNetworks mergingNetworks
elif [[ $choice -eq 5 ]]; then elif [[ $choice -eq 6 ]]; then
echo "${BoldGreen}Currently this feature is not available in this distribution of Beckn ONIX${NC}" echo "${BoldGreen}Currently this feature is not available in this distribution of Beckn ONIX${NC}"
restart_script restart_script
elif [[ $choice -eq 6 ]]; then elif [[ $choice -eq 7 ]]; then
update_network update_network
else else
# Determine the platforms available based on the initial choice # Determine the platforms available based on the initial choice

View File

@@ -0,0 +1,101 @@
services:
# ============================================
# Core Infrastructure Services
# ============================================
# Redis - Caching Service
redis:
image: redis:alpine
container_name: redis
ports:
- "6379:6379"
networks:
- beckn_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
onix-bap:
image: fidedocker/onix-adapter
container_name: onix-bap
platform: linux/amd64
networks:
- beckn_network
ports:
- "8081:8081"
environment:
CONFIG_FILE: "/app/config/local-simple.yaml"
VAULT_ADDR: http://vault:8200
VAULT_TOKEN: root
REDIS_ADDR: redis:6379
RABBITMQ_ADDR: rabbitmq:5672
RABBITMQ_USER: admin
RABBITMQ_PASS: admin123
volumes:
- ../config:/app/config
- ../schemas:/app/schemas
command: ["./server", "--config=/app/config/local-beckn-one-bap.yaml"]
onix-bpp:
image: fidedocker/onix-adapter
container_name: onix-bpp
platform: linux/amd64
networks:
- beckn_network
ports:
- "8082:8082"
environment:
CONFIG_FILE: "/app/config/local-simple.yaml"
VAULT_ADDR: http://vault:8200
VAULT_TOKEN: root
REDIS_ADDR: redis:6379
RABBITMQ_ADDR: rabbitmq:5672
RABBITMQ_USER: admin
RABBITMQ_PASS: admin123
volumes:
- ../config:/app/config
- ../schemas:/app/schemas
command: ["./server", "--config=/app/config/local-beckn-one-bpp.yaml"]
sandbox-bap:
container_name: sandbox-bap
image: fidedocker/sandbox-2.0:latest
platform: linux/amd64
environment:
- NODE_ENV=production
- PORT=3001
ports:
- "3001:3001"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"]
interval: 10s
timeout: 3s
retries: 5
start_period: 10s
networks:
- beckn_network
sandbox-bpp:
container_name: sandbox-bpp
image: fidedocker/sandbox-2.0:latest
platform: linux/amd64
environment:
- NODE_ENV=production
- PORT=3002
ports:
- "3002:3002"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3002/api/health"]
interval: 10s
timeout: 3s
retries: 5
start_period: 10s
networks:
- beckn_network
networks:
beckn_network:
name: beckn_network
driver: bridge

View File

@@ -75,3 +75,4 @@ registry_docker_compose_file=docker-compose-registry.yml
gateway_docker_compose_file=docker-compose-gateway.yml gateway_docker_compose_file=docker-compose-gateway.yml
gcl_docker_compose_file=docker-compose-gcl.yml gcl_docker_compose_file=docker-compose-gcl.yml
adapter_docker_compose_file=docker-compose-adapter.yml adapter_docker_compose_file=docker-compose-adapter.yml
adapter_beckn_one_docker_compose_file=docker-compose-adapter-beckn-one.yml

View File

@@ -1,4 +1,5 @@
docker compose -f docker-compose-adapter.yml down -v docker compose -f docker-compose-adapter.yml down -v
docker compose -f docker-compose-adapter-beckn-one.yml down -v
docker compose -f docker-compose-bap.yml down -v docker compose -f docker-compose-bap.yml down -v
docker compose -f docker-compose-bpp.yml down -v docker compose -f docker-compose-bpp.yml down -v
docker compose -f docker-compose-bpp-with-sandbox.yml down -v docker compose -f docker-compose-bpp-with-sandbox.yml down -v

View File

@@ -19,6 +19,9 @@ registry:
url: "https://dedi-wrapper.example.com/dedi" url: "https://dedi-wrapper.example.com/dedi"
registryName: "subscribers.beckn.one" registryName: "subscribers.beckn.one"
timeout: 30 timeout: 30
retry_max: 3
retry_wait_min: 1s
retry_wait_max: 5s
``` ```
### Configuration Parameters ### Configuration Parameters
@@ -28,6 +31,9 @@ registry:
| `url` | Yes | DeDi wrapper API base URL (include /dedi path) | - | | `url` | Yes | DeDi wrapper API base URL (include /dedi path) | - |
| `registryName` | Yes | Registry name for lookup path | - | | `registryName` | Yes | Registry name for lookup path | - |
| `timeout` | No | Request timeout in seconds | Client default | | `timeout` | No | Request timeout in seconds | Client default |
| `retry_max` | No | Maximum number of retry attempts | 4 (library default) |
| `retry_wait_min` | No | Minimum wait time between retries (e.g., "1s", "500ms") | 1s (library default) |
| `retry_wait_max` | No | Maximum wait time between retries (e.g., "5s") | 30s (library default) |
## API Integration ## API Integration
@@ -88,6 +94,9 @@ modules:
url: "https://dedi-wrapper.example.com/dedi" url: "https://dedi-wrapper.example.com/dedi"
registryName: "subscribers.beckn.one" registryName: "subscribers.beckn.one"
timeout: 30 timeout: 30
retry_max: 3
retry_wait_min: 1s
retry_wait_max: 5s
steps: steps:
- validateSign # Required for registry lookup - validateSign # Required for registry lookup
- addRoute - addRoute

View File

@@ -18,6 +18,9 @@ type Config struct {
URL string `yaml:"url" json:"url"` URL string `yaml:"url" json:"url"`
RegistryName string `yaml:"registryName" json:"registryName"` RegistryName string `yaml:"registryName" json:"registryName"`
Timeout int `yaml:"timeout" json:"timeout"` Timeout int `yaml:"timeout" json:"timeout"`
RetryMax int `yaml:"retry_max" json:"retry_max"`
RetryWaitMin time.Duration `yaml:"retry_wait_min" json:"retry_wait_min"`
RetryWaitMax time.Duration `yaml:"retry_wait_max" json:"retry_wait_max"`
} }
// DeDiRegistryClient encapsulates the logic for calling the DeDi registry endpoints. // DeDiRegistryClient encapsulates the logic for calling the DeDi registry endpoints.
@@ -55,6 +58,17 @@ func New(ctx context.Context, cfg *Config) (*DeDiRegistryClient, func() error, e
retryClient.HTTPClient.Timeout = time.Duration(cfg.Timeout) * time.Second retryClient.HTTPClient.Timeout = time.Duration(cfg.Timeout) * time.Second
} }
// Configure retry settings if provided
if cfg.RetryMax > 0 {
retryClient.RetryMax = cfg.RetryMax
}
if cfg.RetryWaitMin > 0 {
retryClient.RetryWaitMin = cfg.RetryWaitMin
}
if cfg.RetryWaitMax > 0 {
retryClient.RetryWaitMax = cfg.RetryWaitMax
}
client := &DeDiRegistryClient{ client := &DeDiRegistryClient{
config: cfg, config: cfg,
client: retryClient, client: retryClient,

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/beckn-one/beckn-onix/pkg/model" "github.com/beckn-one/beckn-onix/pkg/model"
) )
@@ -84,6 +85,30 @@ func TestNew(t *testing.T) {
if err := closer(); err != nil { if err := closer(); err != nil {
t.Errorf("closer() error = %v", err) t.Errorf("closer() error = %v", err)
} }
t.Run("should apply custom retry settings", func(t *testing.T) {
cfg := &Config{
URL: "http://test.com",
RegistryName: "subscribers.beckn.one",
RetryMax: 10,
RetryWaitMin: 100 * time.Millisecond,
RetryWaitMax: 1 * time.Second,
}
client, _, err := New(ctx, cfg)
if err != nil {
t.Fatalf("expected no error, but got: %v", err)
}
if client.client.RetryMax != cfg.RetryMax {
t.Errorf("expected RetryMax to be %d, but got %d", cfg.RetryMax, client.client.RetryMax)
}
if client.client.RetryWaitMin != cfg.RetryWaitMin {
t.Errorf("expected RetryWaitMin to be %v, but got %v", cfg.RetryWaitMin, client.client.RetryWaitMin)
}
if client.client.RetryWaitMax != cfg.RetryWaitMax {
t.Errorf("expected RetryWaitMax to be %v, but got %v", cfg.RetryWaitMax, client.client.RetryWaitMax)
}
})
} }
func TestLookup(t *testing.T) { func TestLookup(t *testing.T) {

View File

@@ -92,14 +92,23 @@ func (r *Router) loadRules(configPath string) error {
} }
// Build the optimized rule map // Build the optimized rule map
for _, rule := range config.RoutingRules { for _, rule := range config.RoutingRules {
// For v2.x.x, warn if domain is provided and normalize to wildcard "*"
domain := rule.Domain
if isV2Version(rule.Version) {
if domain != "" {
fmt.Printf("WARNING: Domain field '%s' is not needed for version %s and will be ignored. Consider removing it from your config.\n", domain, rule.Version)
}
domain = "*"
}
// Initialize domain map if not exists // Initialize domain map if not exists
if _, ok := r.rules[rule.Domain]; !ok { if _, ok := r.rules[domain]; !ok {
r.rules[rule.Domain] = make(map[string]map[string]*model.Route) r.rules[domain] = make(map[string]map[string]*model.Route)
} }
// Initialize version map if not exists // Initialize version map if not exists
if _, ok := r.rules[rule.Domain][rule.Version]; !ok { if _, ok := r.rules[domain][rule.Version]; !ok {
r.rules[rule.Domain][rule.Version] = make(map[string]*model.Route) r.rules[domain][rule.Version] = make(map[string]*model.Route)
} }
// Add all endpoints for this rule // Add all endpoints for this rule
@@ -137,7 +146,13 @@ func (r *Router) loadRules(configPath string) error {
URL: parsedURL, URL: parsedURL,
} }
} }
r.rules[rule.Domain][rule.Version][endpoint] = route // Check for conflicting v2 rules
if isV2Version(rule.Version) {
if _, exists := r.rules[domain][rule.Version][endpoint]; exists {
return fmt.Errorf("duplicate endpoint '%s' found for version %s. For v2.x.x, domain is ignored, so you can only define each endpoint once per version. Please remove the duplicate rule", endpoint, rule.Version)
}
}
r.rules[domain][rule.Version][endpoint] = route
} }
} }
@@ -147,9 +162,14 @@ func (r *Router) loadRules(configPath string) error {
// validateRules performs basic validation on the loaded routing rules. // validateRules performs basic validation on the loaded routing rules.
func validateRules(rules []routingRule) error { func validateRules(rules []routingRule) error {
for _, rule := range rules { for _, rule := range rules {
// Ensure domain, version, and TargetType are present // Ensure version and TargetType are present
if rule.Domain == "" || rule.Version == "" || rule.TargetType == "" { if rule.Version == "" || rule.TargetType == "" {
return fmt.Errorf("invalid rule: domain, version, and targetType are required") return fmt.Errorf("invalid rule: version and targetType are required")
}
// Domain is required only for v1.x.x
if !isV2Version(rule.Version) && rule.Domain == "" {
return fmt.Errorf("invalid rule: domain is required for version %s", rule.Version)
} }
// Validate based on TargetType // Validate based on TargetType
@@ -197,19 +217,34 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*model.R
// Extract the endpoint from the URL // Extract the endpoint from the URL
endpoint := path.Base(url.Path) endpoint := path.Base(url.Path)
// For v2.x.x, ignore domain and use wildcard; for v1.x.x, use actual domain
domain := requestBody.Context.Domain
if isV2Version(requestBody.Context.Version) {
domain = "*"
}
// Lookup route in the optimized map // Lookup route in the optimized map
domainRules, ok := r.rules[requestBody.Context.Domain] domainRules, ok := r.rules[domain]
if !ok { if !ok {
if domain == "*" {
return nil, fmt.Errorf("no routing rules found for version %s", requestBody.Context.Version)
}
return nil, fmt.Errorf("no routing rules found for domain %s", requestBody.Context.Domain) return nil, fmt.Errorf("no routing rules found for domain %s", requestBody.Context.Domain)
} }
versionRules, ok := domainRules[requestBody.Context.Version] versionRules, ok := domainRules[requestBody.Context.Version]
if !ok { if !ok {
if domain == "*" {
return nil, fmt.Errorf("no routing rules found for version %s", requestBody.Context.Version)
}
return nil, fmt.Errorf("no routing rules found for domain %s version %s", requestBody.Context.Domain, requestBody.Context.Version) return nil, fmt.Errorf("no routing rules found for domain %s version %s", requestBody.Context.Domain, requestBody.Context.Version)
} }
route, ok := versionRules[endpoint] route, ok := versionRules[endpoint]
if !ok { if !ok {
if domain == "*" {
return nil, fmt.Errorf("endpoint '%s' is not supported for version %s in routing config", endpoint, requestBody.Context.Version)
}
return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s in routing config", return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s in routing config",
endpoint, requestBody.Context.Domain, requestBody.Context.Version) endpoint, requestBody.Context.Domain, requestBody.Context.Version)
} }
@@ -252,3 +287,8 @@ func joinPath(u *url.URL, endpoint string) string {
} }
return path.Join(u.Path, endpoint) return path.Join(u.Path, endpoint)
} }
// isV2Version checks if the version is 2.x.x
func isV2Version(version string) bool {
return strings.HasPrefix(version, "2.")
}

View File

@@ -124,7 +124,7 @@ func TestLoadRules(t *testing.T) {
// Expected router.rules map structure based on the yaml. // Expected router.rules map structure based on the yaml.
expectedRules := map[string]map[string]map[string]*model.Route{ expectedRules := map[string]map[string]map[string]*model.Route{
"ONDC:TRV10": { "ONDC:TRV10": {
"2.0.0": { "1.1.0": {
"search": {TargetType: targetTypeURL, URL: parseURL(t, "https://mock_gateway.com/v2/ondc/search")}, "search": {TargetType: targetTypeURL, URL: parseURL(t, "https://mock_gateway.com/v2/ondc/search")},
"init": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/init")}, "init": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/init")},
"select": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/select")}, "select": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/select")},
@@ -291,7 +291,7 @@ func TestValidateRulesFailure(t *testing.T) {
Endpoints: []string{"search", "select"}, Endpoints: []string{"search", "select"},
}, },
}, },
wantErr: "invalid rule: domain, version, and targetType are required", wantErr: "invalid rule: domain is required for version 1.0.0",
}, },
{ {
name: "Missing version", name: "Missing version",
@@ -305,7 +305,7 @@ func TestValidateRulesFailure(t *testing.T) {
Endpoints: []string{"search", "select"}, Endpoints: []string{"search", "select"},
}, },
}, },
wantErr: "invalid rule: domain, version, and targetType are required", wantErr: "invalid rule: version and targetType are required",
}, },
{ {
name: "Missing targetType", name: "Missing targetType",
@@ -319,7 +319,7 @@ func TestValidateRulesFailure(t *testing.T) {
Endpoints: []string{"search", "select"}, Endpoints: []string{"search", "select"},
}, },
}, },
wantErr: "invalid rule: domain, version, and targetType are required", wantErr: "invalid rule: version and targetType are required",
}, },
{ {
name: "Invalid targetType", name: "Invalid targetType",
@@ -438,37 +438,37 @@ func TestRouteSuccess(t *testing.T) {
name: "Valid domain, version, and endpoint (bpp routing with gateway URL)", name: "Valid domain, version, and endpoint (bpp routing with gateway URL)",
configFile: "bap_caller.yaml", configFile: "bap_caller.yaml",
url: "https://example.com/v1/ondc/search", url: "https://example.com/v1/ondc/search",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0"}}`,
}, },
{ {
name: "Valid domain, version, and endpoint (bpp routing with bpp_uri)", name: "Valid domain, version, and endpoint (bpp routing with bpp_uri)",
configFile: "bap_caller.yaml", configFile: "bap_caller.yaml",
url: "https://example.com/v1/ondc/select", url: "https://example.com/v1/ondc/select",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bpp_uri": "https://bpp1.example.com"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0", "bpp_uri": "https://bpp1.example.com"}}`,
}, },
{ {
name: "Valid domain, version, and endpoint (url routing)", name: "Valid domain, version, and endpoint (url routing)",
configFile: "bpp_receiver.yaml", configFile: "bpp_receiver.yaml",
url: "https://example.com/v1/ondc/select", url: "https://example.com/v1/ondc/select",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0"}}`,
}, },
{ {
name: "Valid domain, version, and endpoint (publisher routing)", name: "Valid domain, version, and endpoint (publisher routing)",
configFile: "bpp_receiver.yaml", configFile: "bpp_receiver.yaml",
url: "https://example.com/v1/ondc/search", url: "https://example.com/v1/ondc/search",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0"}}`,
}, },
{ {
name: "Valid domain, version, and endpoint (bap routing with bap_uri)", name: "Valid domain, version, and endpoint (bap routing with bap_uri)",
configFile: "bpp_caller.yaml", configFile: "bpp_caller.yaml",
url: "https://example.com/v1/ondc/on_select", url: "https://example.com/v1/ondc/on_select",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bap_uri": "https://bap1.example.com"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0", "bap_uri": "https://bap1.example.com"}}`,
}, },
{ {
name: "Valid domain, version, and endpoint (bpp routing with bpp_uri)", name: "Valid domain, version, and endpoint (bpp routing with bpp_uri)",
configFile: "bap_receiver.yaml", configFile: "bap_receiver.yaml",
url: "https://example.com/v1/ondc/on_select", url: "https://example.com/v1/ondc/on_select",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bpp_uri": "https://bpp1.example.com"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0", "bpp_uri": "https://bpp1.example.com"}}`,
}, },
} }
@@ -504,35 +504,35 @@ func TestRouteFailure(t *testing.T) {
name: "Unsupported endpoint", name: "Unsupported endpoint",
configFile: "bpp_receiver.yaml", configFile: "bpp_receiver.yaml",
url: "https://example.com/v1/ondc/unsupported", url: "https://example.com/v1/ondc/unsupported",
body: `{"context": {"domain": "ONDC:TRV11", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV11", "version": "1.1.0"}}`,
wantErr: "endpoint 'unsupported' is not supported for domain ONDC:TRV11 and version 2.0.0", wantErr: "endpoint 'unsupported' is not supported for domain ONDC:TRV11 and version 1.1.0",
}, },
{ {
name: "No matching rule", name: "No matching rule",
configFile: "bpp_receiver.yaml", configFile: "bpp_receiver.yaml",
url: "https://example.com/v1/ondc/select", url: "https://example.com/v1/ondc/select",
body: `{"context": {"domain": "ONDC:SRV11", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:SRV11", "version": "1.1.0"}}`,
wantErr: "no routing rules found for domain ONDC:SRV11", wantErr: "no routing rules found for domain ONDC:SRV11",
}, },
{ {
name: "Missing bap_uri for bap routing", name: "Missing bap_uri for bap routing",
configFile: "bpp_caller.yaml", configFile: "bpp_caller.yaml",
url: "https://example.com/v1/ondc/on_search", url: "https://example.com/v1/ondc/on_search",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0"}}`,
wantErr: "could not determine destination for endpoint 'on_search': neither request contained a BAP URI nor was a default URL configured in routing rules", wantErr: "could not determine destination for endpoint 'on_search': neither request contained a BAP URI nor was a default URL configured in routing rules",
}, },
{ {
name: "Missing bpp_uri for bpp routing", name: "Missing bpp_uri for bpp routing",
configFile: "bap_caller.yaml", configFile: "bap_caller.yaml",
url: "https://example.com/v1/ondc/select", url: "https://example.com/v1/ondc/select",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0"}}`,
wantErr: "could not determine destination for endpoint 'select': neither request contained a BPP URI nor was a default URL configured in routing rules", wantErr: "could not determine destination for endpoint 'select': neither request contained a BPP URI nor was a default URL configured in routing rules",
}, },
{ {
name: "Invalid bpp_uri format in request", name: "Invalid bpp_uri format in request",
configFile: "bap_caller.yaml", configFile: "bap_caller.yaml",
url: "https://example.com/v1/ondc/select", url: "https://example.com/v1/ondc/select",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bpp_uri": "htp:// invalid-url"}}`, // Invalid scheme (htp instead of http) body: `{"context": {"domain": "ONDC:TRV10", "version": "1.1.0", "bpp_uri": "htp:// invalid-url"}}`, // Invalid scheme (htp instead of http)
wantErr: `invalid BPP URI - htp:// invalid-url in request body for select: parse "htp:// invalid-url": invalid character " " in host name`, wantErr: `invalid BPP URI - htp:// invalid-url in request body for select: parse "htp:// invalid-url": invalid character " " in host name`,
}, },
} }
@@ -565,7 +565,7 @@ func TestExcludeAction(t *testing.T) {
configFile: "exclude_action_true.yaml", configFile: "exclude_action_true.yaml",
expectedRoutes: map[string]map[string]map[string]*model.Route{ expectedRoutes: map[string]map[string]map[string]*model.Route{
"ONDC:TRV10": { "ONDC:TRV10": {
"2.0.0": { "1.1.0": {
"search": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc")}, "search": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc")},
"init": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc")}, "init": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc")},
}, },
@@ -577,7 +577,7 @@ func TestExcludeAction(t *testing.T) {
configFile: "exclude_action_false.yaml", configFile: "exclude_action_false.yaml",
expectedRoutes: map[string]map[string]map[string]*model.Route{ expectedRoutes: map[string]map[string]map[string]*model.Route{
"ONDC:TRV10": { "ONDC:TRV10": {
"2.0.0": { "1.1.0": {
"search": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/search")}, "search": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/search")},
"init": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/init")}, "init": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/init")},
}, },
@@ -589,7 +589,7 @@ func TestExcludeAction(t *testing.T) {
configFile: "exclude_action_default.yaml", configFile: "exclude_action_default.yaml",
expectedRoutes: map[string]map[string]map[string]*model.Route{ expectedRoutes: map[string]map[string]map[string]*model.Route{
"ONDC:TRV10": { "ONDC:TRV10": {
"2.0.0": { "1.1.0": {
"search": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/search")}, "search": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/search")},
"init": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/init")}, "init": {TargetType: targetTypeURL, URL: parseURL(t, "https://services-backend.com/v2/ondc/init")},
}, },
@@ -630,7 +630,7 @@ func TestExcludeActionWithNonURLTargetTypes(t *testing.T) {
configFile: "exclude_action_bpp.yaml", configFile: "exclude_action_bpp.yaml",
expectedRoutes: map[string]map[string]map[string]*model.Route{ expectedRoutes: map[string]map[string]map[string]*model.Route{
"ONDC:TRV10": { "ONDC:TRV10": {
"2.0.0": { "1.1.0": {
"search": {TargetType: targetTypeBPP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/search")}, "search": {TargetType: targetTypeBPP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/search")},
"init": {TargetType: targetTypeBPP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/init")}, "init": {TargetType: targetTypeBPP, URL: parseURL(t, "https://mock_bpp.com/v2/ondc/init")},
}, },
@@ -642,7 +642,7 @@ func TestExcludeActionWithNonURLTargetTypes(t *testing.T) {
configFile: "exclude_action_bap.yaml", configFile: "exclude_action_bap.yaml",
expectedRoutes: map[string]map[string]map[string]*model.Route{ expectedRoutes: map[string]map[string]map[string]*model.Route{
"ONDC:TRV10": { "ONDC:TRV10": {
"2.0.0": { "1.1.0": {
"search": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bap.com/v2/ondc/search")}, "search": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bap.com/v2/ondc/search")},
"init": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bap.com/v2/ondc/init")}, "init": {TargetType: targetTypeBAP, URL: parseURL(t, "https://mock_bap.com/v2/ondc/init")},
}, },
@@ -654,7 +654,7 @@ func TestExcludeActionWithNonURLTargetTypes(t *testing.T) {
configFile: "exclude_action_publisher.yaml", configFile: "exclude_action_publisher.yaml",
expectedRoutes: map[string]map[string]map[string]*model.Route{ expectedRoutes: map[string]map[string]map[string]*model.Route{
"ONDC:TRV10": { "ONDC:TRV10": {
"2.0.0": { "1.1.0": {
"search": {TargetType: targetTypePublisher, PublisherID: "test_topic", URL: nil}, "search": {TargetType: targetTypePublisher, PublisherID: "test_topic", URL: nil},
"init": {TargetType: targetTypePublisher, PublisherID: "test_topic", URL: nil}, "init": {TargetType: targetTypePublisher, PublisherID: "test_topic", URL: nil},
}, },
@@ -682,3 +682,112 @@ func TestExcludeActionWithNonURLTargetTypes(t *testing.T) {
}) })
} }
} }
// TestV2RouteSuccess tests v2 routing with domain-agnostic behavior
func TestV2RouteSuccess(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
configFile string
url string
body string
}{
{
name: "v2 BAP caller - domain ignored",
configFile: "v2_bap_caller.yaml",
url: "https://example.com/v2/search",
body: `{"context": {"domain": "any_domain", "version": "2.0.0"}}`,
},
{
name: "v2 BPP receiver - domain ignored",
configFile: "v2_bpp_receiver.yaml",
url: "https://example.com/v2/select",
body: `{"context": {"domain": "different_domain", "version": "2.0.0"}}`,
},
{
name: "v2 request without domain field",
configFile: "v2_bap_caller.yaml",
url: "https://example.com/v2/search",
body: `{"context": {"version": "2.0.0"}}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router, _, rulesFilePath := setupRouter(t, tt.configFile)
defer os.RemoveAll(filepath.Dir(rulesFilePath))
parsedURL, _ := url.Parse(tt.url)
_, err := router.Route(ctx, parsedURL, []byte(tt.body))
if err != nil {
t.Errorf("router.Route() = %v, want nil (domain should be ignored for v2)", err)
}
})
}
}
// TestV2ConflictingRules tests that conflicting v2 rules are detected at load time
func TestV2ConflictingRules(t *testing.T) {
router := &Router{
rules: make(map[string]map[string]map[string]*model.Route),
}
configDir := t.TempDir()
conflictingConfig := `routingRules:
- version: 2.0.0
targetType: bap
endpoints:
- on_search
- version: 2.0.0
targetType: bap
endpoints:
- on_search
`
rulesPath := filepath.Join(configDir, "conflicting_rules.yaml")
if err := os.WriteFile(rulesPath, []byte(conflictingConfig), 0644); err != nil {
t.Fatalf("WriteFile() err = %v, want nil", err)
}
defer os.RemoveAll(configDir)
err := router.loadRules(rulesPath)
if err == nil {
t.Error("loadRules() with conflicting v2 rules should return error, got nil")
}
expectedErr := "duplicate endpoint 'on_search' found for version 2.0.0"
if err != nil && !strings.Contains(err.Error(), expectedErr) {
t.Errorf("loadRules() error = %v, want error containing %q", err, expectedErr)
}
}
// TestV1DomainRequired tests that domain is required for v1 configs
func TestV1DomainRequired(t *testing.T) {
router := &Router{
rules: make(map[string]map[string]map[string]*model.Route),
}
configDir := t.TempDir()
v1ConfigWithoutDomain := `routingRules:
- version: 1.0.0
targetType: bap
endpoints:
- on_search
`
rulesPath := filepath.Join(configDir, "v1_no_domain.yaml")
if err := os.WriteFile(rulesPath, []byte(v1ConfigWithoutDomain), 0644); err != nil {
t.Fatalf("WriteFile() err = %v, want nil", err)
}
defer os.RemoveAll(configDir)
err := router.loadRules(rulesPath)
if err == nil {
t.Error("loadRules() with v1 config without domain should fail, got nil")
}
expectedErr := "invalid rule: domain is required for version 1.0.0"
if err != nil && !strings.Contains(err.Error(), expectedErr) {
t.Errorf("loadRules() error = %v, want error containing %q", err, expectedErr)
}
}

View File

@@ -1,13 +1,13 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: bpp targetType: bpp
target: target:
url: https://gateway.example.com url: https://gateway.example.com
endpoints: endpoints:
- search - search
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: bpp targetType: bpp
endpoints: endpoints:
- select - select
@@ -16,7 +16,7 @@ routingRules:
- status - status
- cancel - cancel
- domain: ONDC:TRV12 - domain: ONDC:TRV12
version: 2.0.0 version: 1.1.0
targetType: bpp targetType: bpp
endpoints: endpoints:
- select - select

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: url targetType: url
target: target:
url: https://services-backend/trv/v1 url: https://services-backend/trv/v1
@@ -12,7 +12,7 @@ routingRules:
- on_update - on_update
- on_cancel - on_cancel
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: publisher targetType: publisher
target: target:
publisherId: trv_topic_id1 publisherId: trv_topic_id1

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: bap targetType: bap
endpoints: endpoints:
- on_search - on_search
@@ -11,7 +11,7 @@ routingRules:
- on_update - on_update
- on_cancel - on_cancel
- domain: ONDC:TRV11 - domain: ONDC:TRV11
version: 2.0.0 version: 1.1.0
targetType: bap targetType: bap
endpoints: endpoints:
- on_search - on_search

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: url targetType: url
target: target:
url: https://services-backend/trv/v1 url: https://services-backend/trv/v1
@@ -11,14 +11,14 @@ routingRules:
- status - status
- cancel - cancel
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: publisher targetType: publisher
target: target:
publisherId: trv_topic_id1 publisherId: trv_topic_id1
endpoints: endpoints:
- search - search
- domain: ONDC:TRV11 - domain: ONDC:TRV11
version: 2.0.0 version: 1.1.0
targetType: url targetType: url
target: target:
url: https://services-backend/trv/v1 url: https://services-backend/trv/v1

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: bap targetType: bap
target: target:
url: https://mock_bap.com/v2/ondc url: https://mock_bap.com/v2/ondc

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: bpp targetType: bpp
target: target:
url: https://mock_bpp.com/v2/ondc url: https://mock_bpp.com/v2/ondc

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: url targetType: url
target: target:
url: https://services-backend.com/v2/ondc url: https://services-backend.com/v2/ondc

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: url targetType: url
target: target:
url: https://services-backend.com/v2/ondc url: https://services-backend.com/v2/ondc

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: publisher targetType: publisher
target: target:
publisherId: test_topic publisherId: test_topic

View File

@@ -1,6 +1,6 @@
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: url targetType: url
target: target:
url: https://services-backend.com/v2/ondc url: https://services-backend.com/v2/ondc

View File

@@ -0,0 +1,15 @@
routingRules:
- version: 2.0.0
targetType: bpp
target:
url: https://gateway.example.com
endpoints:
- search
- version: 2.0.0
targetType: bpp
endpoints:
- select
- init
- confirm
- status
- cancel

View File

@@ -0,0 +1,17 @@
routingRules:
- version: 2.0.0
targetType: url
target:
url: https://services-backend/trv/v2
endpoints:
- select
- init
- confirm
- status
- cancel
- version: 2.0.0
targetType: publisher
target:
publisherId: trv_topic_v2
endpoints:
- search

View File

@@ -1,14 +1,14 @@
# testData/all_route_types.yaml # testData/all_route_types.yaml
routingRules: routingRules:
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: url targetType: url
target: target:
url: https://mock_gateway.com/v2/ondc url: https://mock_gateway.com/v2/ondc
endpoints: endpoints:
- search - search
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: bap targetType: bap
target: target:
url: https://mock_bpp.com/v2/ondc url: https://mock_bpp.com/v2/ondc
@@ -16,14 +16,14 @@ routingRules:
- init - init
- select - select
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: publisher targetType: publisher
target: target:
publisherId: beckn_onix_topic publisherId: beckn_onix_topic
endpoints: endpoints:
- confirm - confirm
- domain: ONDC:TRV10 - domain: ONDC:TRV10
version: 2.0.0 version: 1.1.0
targetType: bap targetType: bap
target: target:
url: https://mock_bap_gateway.com/v2/ondc url: https://mock_bap_gateway.com/v2/ondc