diff --git a/CONFIG.md b/CONFIG.md index 6df5e92..34ddea1 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -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. @@ -711,9 +738,43 @@ routingRules: #### `domain` **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`) +**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` **Type**: `string` **Required**: Yes @@ -881,6 +942,71 @@ routingRules: **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 diff --git a/README.md b/README.md index b7521b3..cd37da2 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Resources: #### 3. **Plugin Types** - **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 - **SignValidator**: Ed25519 signature validation for incoming requests - **SchemaValidator**: JSON schema validation @@ -239,12 +239,18 @@ This automated script will: - Start ONIX adapter in Docker - 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 # from the repository root docker build -f Dockerfile.adapter-with-plugins -t beckn-onix:latest . ``` + +**For detailed setup instructions, see [SETUP.md](SETUP.md)** + **Services Started:** - Redis: localhost:6379 - ONIX Adapter: http://localhost:8081 diff --git a/SETUP.md b/SETUP.md index fb563a4..5d1bda8 100644 --- a/SETUP.md +++ b/SETUP.md @@ -88,14 +88,53 @@ This will automatically: **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 # from the repository root 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: @@ -104,7 +143,8 @@ cd beckn-onix/install chmod +x 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: @@ -125,7 +165,10 @@ This will automatically: - ONIX Adapter: http://localhost:8081 - 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 # from the repository root @@ -193,6 +236,7 @@ This creates `.so` files in the `plugins/` directory: - `signer.so` - Message signing - `signvalidator.so` - Signature validation - `schemavalidator.so` - JSON schema validation +- `schemav2validator.so` - OpenAPI 3.x schema validation - `keymanager.so` - Vault integration - `simplekeymanager.so` - Simple key management (no Vault) - `publisher.so` - RabbitMQ publishing @@ -865,29 +909,39 @@ router: ### 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 routingRules: + # v1.x.x - domain is required - domain: "ONDC:RET10" version: "1.0.0" targetType: "url" # or "publisher" target: url: "https://seller.example.com/beckn" - # OR for async - # queueName: "retail_queue" - # routingKey: "retail.*" endpoints: - search - select - init - confirm - headers: # Optional additional headers - X-Custom-Header: "value" - timeout: 60 # seconds - retryPolicy: - maxRetries: 3 - backoff: exponential + + # v2.x.x - domain is optional (domain-agnostic routing) + - version: "2.0.0" + targetType: "url" + target: + 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 Available steps for configuration: diff --git a/config/local-beckn-one-bap.yaml b/config/local-beckn-one-bap.yaml new file mode 100644 index 0000000..fb63f66 --- /dev/null +++ b/config/local-beckn-one-bap.yaml @@ -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 + \ No newline at end of file diff --git a/config/local-beckn-one-bpp.yaml b/config/local-beckn-one-bpp.yaml new file mode 100644 index 0000000..77a57aa --- /dev/null +++ b/config/local-beckn-one-bpp.yaml @@ -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 diff --git a/config/local-beckn-one-routing-BAPCaller.yaml b/config/local-beckn-one-routing-BAPCaller.yaml new file mode 100644 index 0000000..f0b9954 --- /dev/null +++ b/config/local-beckn-one-routing-BAPCaller.yaml @@ -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 diff --git a/config/local-beckn-one-routing-BAPReceiver.yaml b/config/local-beckn-one-routing-BAPReceiver.yaml new file mode 100644 index 0000000..a3659ff --- /dev/null +++ b/config/local-beckn-one-routing-BAPReceiver.yaml @@ -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 diff --git a/config/local-beckn-one-routing-BPPCaller.yaml b/config/local-beckn-one-routing-BPPCaller.yaml new file mode 100644 index 0000000..cfa2f6d --- /dev/null +++ b/config/local-beckn-one-routing-BPPCaller.yaml @@ -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 \ No newline at end of file diff --git a/config/local-beckn-one-routing-BPPReceiver.yaml b/config/local-beckn-one-routing-BPPReceiver.yaml new file mode 100644 index 0000000..860544d --- /dev/null +++ b/config/local-beckn-one-routing-BPPReceiver.yaml @@ -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 \ No newline at end of file diff --git a/install/beckn-onix.sh b/install/beckn-onix.sh index fd906dd..cd961f0 100755 --- a/install/beckn-onix.sh +++ b/install/beckn-onix.sh @@ -928,6 +928,58 @@ install_bap_adapter() { install_bpp_adapter() { 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 echo "Welcome to Beckn-ONIX!" @@ -941,29 +993,35 @@ echo "Checking prerequisites of Beckn-ONIX deployment" check_docker_permissions 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 -validate_input "$choice" 6 +validate_input "$choice" 7 if [[ $? -ne 0 ]]; then restart_script # Restart the script if input is invalid fi 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_registry install_gateway install_bap_protocol_server install_bpp_protocol_server_with_sandbox install_adapter "BOTH" -elif [[ $choice -eq 4 ]]; then +elif [[ $choice -eq 5 ]]; then echo "Determining the platforms available based on the initial choice" 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}" restart_script -elif [[ $choice -eq 6 ]]; then +elif [[ $choice -eq 7 ]]; then update_network else # Determine the platforms available based on the initial choice diff --git a/install/docker-compose-adapter-beckn-one.yml b/install/docker-compose-adapter-beckn-one.yml new file mode 100644 index 0000000..f5785a1 --- /dev/null +++ b/install/docker-compose-adapter-beckn-one.yml @@ -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 diff --git a/install/scripts/variables.sh b/install/scripts/variables.sh index e667e03..32fb282 100755 --- a/install/scripts/variables.sh +++ b/install/scripts/variables.sh @@ -74,4 +74,5 @@ bap_docker_compose_file=docker-compose-bap.yml registry_docker_compose_file=docker-compose-registry.yml gateway_docker_compose_file=docker-compose-gateway.yml gcl_docker_compose_file=docker-compose-gcl.yml -adapter_docker_compose_file=docker-compose-adapter.yml \ No newline at end of file +adapter_docker_compose_file=docker-compose-adapter.yml +adapter_beckn_one_docker_compose_file=docker-compose-adapter-beckn-one.yml \ No newline at end of file diff --git a/install/uninstall.sh b/install/uninstall.sh index 98e9d91..ba2e529 100755 --- a/install/uninstall.sh +++ b/install/uninstall.sh @@ -1,4 +1,5 @@ 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-bpp.yml down -v docker compose -f docker-compose-bpp-with-sandbox.yml down -v diff --git a/pkg/plugin/implementation/dediregistry/README.md b/pkg/plugin/implementation/dediregistry/README.md index d26a66c..e302559 100644 --- a/pkg/plugin/implementation/dediregistry/README.md +++ b/pkg/plugin/implementation/dediregistry/README.md @@ -19,6 +19,9 @@ registry: url: "https://dedi-wrapper.example.com/dedi" registryName: "subscribers.beckn.one" timeout: 30 + retry_max: 3 + retry_wait_min: 1s + retry_wait_max: 5s ``` ### Configuration Parameters @@ -28,6 +31,9 @@ registry: | `url` | Yes | DeDi wrapper API base URL (include /dedi path) | - | | `registryName` | Yes | Registry name for lookup path | - | | `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 @@ -88,6 +94,9 @@ modules: url: "https://dedi-wrapper.example.com/dedi" registryName: "subscribers.beckn.one" timeout: 30 + retry_max: 3 + retry_wait_min: 1s + retry_wait_max: 5s steps: - validateSign # Required for registry lookup - addRoute diff --git a/pkg/plugin/implementation/dediregistry/dediregistry.go b/pkg/plugin/implementation/dediregistry/dediregistry.go index 4a41d4e..8bc4853 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry.go @@ -15,9 +15,12 @@ import ( // Config holds configuration parameters for the DeDi registry client. type Config struct { - URL string `yaml:"url" json:"url"` - RegistryName string `yaml:"registryName" json:"registryName"` - Timeout int `yaml:"timeout" json:"timeout"` + URL string `yaml:"url" json:"url"` + RegistryName string `yaml:"registryName" json:"registryName"` + 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. @@ -55,6 +58,17 @@ func New(ctx context.Context, cfg *Config) (*DeDiRegistryClient, func() error, e 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{ config: cfg, client: retryClient, diff --git a/pkg/plugin/implementation/dediregistry/dediregistry_test.go b/pkg/plugin/implementation/dediregistry/dediregistry_test.go index 01737ec..e81ddda 100644 --- a/pkg/plugin/implementation/dediregistry/dediregistry_test.go +++ b/pkg/plugin/implementation/dediregistry/dediregistry_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/beckn-one/beckn-onix/pkg/model" ) @@ -84,6 +85,30 @@ func TestNew(t *testing.T) { if err := closer(); err != nil { 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) { diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index d469975..a42a4af 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -92,14 +92,23 @@ func (r *Router) loadRules(configPath string) error { } // Build the optimized rule map 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 - if _, ok := r.rules[rule.Domain]; !ok { - r.rules[rule.Domain] = make(map[string]map[string]*model.Route) + if _, ok := r.rules[domain]; !ok { + r.rules[domain] = make(map[string]map[string]*model.Route) } // Initialize version map if not exists - if _, ok := r.rules[rule.Domain][rule.Version]; !ok { - r.rules[rule.Domain][rule.Version] = make(map[string]*model.Route) + if _, ok := r.rules[domain][rule.Version]; !ok { + r.rules[domain][rule.Version] = make(map[string]*model.Route) } // Add all endpoints for this rule @@ -137,7 +146,13 @@ func (r *Router) loadRules(configPath string) error { 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. func validateRules(rules []routingRule) error { for _, rule := range rules { - // Ensure domain, version, and TargetType are present - if rule.Domain == "" || rule.Version == "" || rule.TargetType == "" { - return fmt.Errorf("invalid rule: domain, version, and targetType are required") + // Ensure version and TargetType are present + if rule.Version == "" || rule.TargetType == "" { + 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 @@ -197,19 +217,34 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*model.R // Extract the endpoint from the URL 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 - domainRules, ok := r.rules[requestBody.Context.Domain] + domainRules, ok := r.rules[domain] 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) } versionRules, ok := domainRules[requestBody.Context.Version] 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) } route, ok := versionRules[endpoint] 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", endpoint, requestBody.Context.Domain, requestBody.Context.Version) } @@ -251,4 +286,9 @@ func joinPath(u *url.URL, endpoint string) string { u.Path = "/" } 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.") } \ No newline at end of file diff --git a/pkg/plugin/implementation/router/router_test.go b/pkg/plugin/implementation/router/router_test.go index 1045d0f..8642e61 100644 --- a/pkg/plugin/implementation/router/router_test.go +++ b/pkg/plugin/implementation/router/router_test.go @@ -124,7 +124,7 @@ func TestLoadRules(t *testing.T) { // Expected router.rules map structure based on the yaml. expectedRules := map[string]map[string]map[string]*model.Route{ "ONDC:TRV10": { - "2.0.0": { + "1.1.0": { "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")}, "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"}, }, }, - wantErr: "invalid rule: domain, version, and targetType are required", + wantErr: "invalid rule: domain is required for version 1.0.0", }, { name: "Missing version", @@ -305,7 +305,7 @@ func TestValidateRulesFailure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - wantErr: "invalid rule: domain, version, and targetType are required", + wantErr: "invalid rule: version and targetType are required", }, { name: "Missing targetType", @@ -319,7 +319,7 @@ func TestValidateRulesFailure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - wantErr: "invalid rule: domain, version, and targetType are required", + wantErr: "invalid rule: version and targetType are required", }, { name: "Invalid targetType", @@ -438,37 +438,37 @@ func TestRouteSuccess(t *testing.T) { name: "Valid domain, version, and endpoint (bpp routing with gateway URL)", configFile: "bap_caller.yaml", 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)", configFile: "bap_caller.yaml", 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)", configFile: "bpp_receiver.yaml", 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)", configFile: "bpp_receiver.yaml", 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)", configFile: "bpp_caller.yaml", 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)", configFile: "bap_receiver.yaml", 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", configFile: "bpp_receiver.yaml", url: "https://example.com/v1/ondc/unsupported", - body: `{"context": {"domain": "ONDC:TRV11", "version": "2.0.0"}}`, - wantErr: "endpoint 'unsupported' is not supported for domain ONDC:TRV11 and 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 1.1.0", }, { name: "No matching rule", configFile: "bpp_receiver.yaml", 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", }, { name: "Missing bap_uri for bap routing", configFile: "bpp_caller.yaml", 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", }, { name: "Missing bpp_uri for bpp routing", configFile: "bap_caller.yaml", 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", }, { name: "Invalid bpp_uri format in request", configFile: "bap_caller.yaml", 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`, }, } @@ -565,7 +565,7 @@ func TestExcludeAction(t *testing.T) { configFile: "exclude_action_true.yaml", expectedRoutes: map[string]map[string]map[string]*model.Route{ "ONDC:TRV10": { - "2.0.0": { + "1.1.0": { "search": {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", expectedRoutes: map[string]map[string]map[string]*model.Route{ "ONDC:TRV10": { - "2.0.0": { + "1.1.0": { "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")}, }, @@ -589,7 +589,7 @@ func TestExcludeAction(t *testing.T) { configFile: "exclude_action_default.yaml", expectedRoutes: map[string]map[string]map[string]*model.Route{ "ONDC:TRV10": { - "2.0.0": { + "1.1.0": { "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")}, }, @@ -630,7 +630,7 @@ func TestExcludeActionWithNonURLTargetTypes(t *testing.T) { configFile: "exclude_action_bpp.yaml", expectedRoutes: map[string]map[string]map[string]*model.Route{ "ONDC:TRV10": { - "2.0.0": { + "1.1.0": { "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")}, }, @@ -642,7 +642,7 @@ func TestExcludeActionWithNonURLTargetTypes(t *testing.T) { configFile: "exclude_action_bap.yaml", expectedRoutes: map[string]map[string]map[string]*model.Route{ "ONDC:TRV10": { - "2.0.0": { + "1.1.0": { "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")}, }, @@ -654,7 +654,7 @@ func TestExcludeActionWithNonURLTargetTypes(t *testing.T) { configFile: "exclude_action_publisher.yaml", expectedRoutes: map[string]map[string]map[string]*model.Route{ "ONDC:TRV10": { - "2.0.0": { + "1.1.0": { "search": {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) + } +} diff --git a/pkg/plugin/implementation/router/testData/bap_caller.yaml b/pkg/plugin/implementation/router/testData/bap_caller.yaml index 6a40a0f..f7c1c33 100644 --- a/pkg/plugin/implementation/router/testData/bap_caller.yaml +++ b/pkg/plugin/implementation/router/testData/bap_caller.yaml @@ -1,13 +1,13 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: bpp target: url: https://gateway.example.com endpoints: - search - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: bpp endpoints: - select @@ -16,7 +16,7 @@ routingRules: - status - cancel - domain: ONDC:TRV12 - version: 2.0.0 + version: 1.1.0 targetType: bpp endpoints: - select diff --git a/pkg/plugin/implementation/router/testData/bap_receiver.yaml b/pkg/plugin/implementation/router/testData/bap_receiver.yaml index 17432db..bd6595e 100644 --- a/pkg/plugin/implementation/router/testData/bap_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bap_receiver.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: url target: url: https://services-backend/trv/v1 @@ -12,7 +12,7 @@ routingRules: - on_update - on_cancel - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: publisher target: publisherId: trv_topic_id1 diff --git a/pkg/plugin/implementation/router/testData/bpp_caller.yaml b/pkg/plugin/implementation/router/testData/bpp_caller.yaml index 339b92d..039516b 100644 --- a/pkg/plugin/implementation/router/testData/bpp_caller.yaml +++ b/pkg/plugin/implementation/router/testData/bpp_caller.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: bap endpoints: - on_search @@ -11,7 +11,7 @@ routingRules: - on_update - on_cancel - domain: ONDC:TRV11 - version: 2.0.0 + version: 1.1.0 targetType: bap endpoints: - on_search diff --git a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml index 7355592..df0e7bd 100644 --- a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: url target: url: https://services-backend/trv/v1 @@ -11,14 +11,14 @@ routingRules: - status - cancel - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: publisher target: publisherId: trv_topic_id1 endpoints: - search - domain: ONDC:TRV11 - version: 2.0.0 + version: 1.1.0 targetType: url target: url: https://services-backend/trv/v1 diff --git a/pkg/plugin/implementation/router/testData/exclude_action_bap.yaml b/pkg/plugin/implementation/router/testData/exclude_action_bap.yaml index be4a724..2a075de 100644 --- a/pkg/plugin/implementation/router/testData/exclude_action_bap.yaml +++ b/pkg/plugin/implementation/router/testData/exclude_action_bap.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: bap target: url: https://mock_bap.com/v2/ondc diff --git a/pkg/plugin/implementation/router/testData/exclude_action_bpp.yaml b/pkg/plugin/implementation/router/testData/exclude_action_bpp.yaml index f3a515b..53fac4c 100644 --- a/pkg/plugin/implementation/router/testData/exclude_action_bpp.yaml +++ b/pkg/plugin/implementation/router/testData/exclude_action_bpp.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: bpp target: url: https://mock_bpp.com/v2/ondc diff --git a/pkg/plugin/implementation/router/testData/exclude_action_default.yaml b/pkg/plugin/implementation/router/testData/exclude_action_default.yaml index a3da856..f43caa1 100644 --- a/pkg/plugin/implementation/router/testData/exclude_action_default.yaml +++ b/pkg/plugin/implementation/router/testData/exclude_action_default.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: url target: url: https://services-backend.com/v2/ondc diff --git a/pkg/plugin/implementation/router/testData/exclude_action_false.yaml b/pkg/plugin/implementation/router/testData/exclude_action_false.yaml index 3e130e3..ed2aca4 100644 --- a/pkg/plugin/implementation/router/testData/exclude_action_false.yaml +++ b/pkg/plugin/implementation/router/testData/exclude_action_false.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: url target: url: https://services-backend.com/v2/ondc diff --git a/pkg/plugin/implementation/router/testData/exclude_action_publisher.yaml b/pkg/plugin/implementation/router/testData/exclude_action_publisher.yaml index 1032a4c..358619e 100644 --- a/pkg/plugin/implementation/router/testData/exclude_action_publisher.yaml +++ b/pkg/plugin/implementation/router/testData/exclude_action_publisher.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: publisher target: publisherId: test_topic diff --git a/pkg/plugin/implementation/router/testData/exclude_action_true.yaml b/pkg/plugin/implementation/router/testData/exclude_action_true.yaml index 3a75ebd..44c3ac5 100644 --- a/pkg/plugin/implementation/router/testData/exclude_action_true.yaml +++ b/pkg/plugin/implementation/router/testData/exclude_action_true.yaml @@ -1,6 +1,6 @@ routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: url target: url: https://services-backend.com/v2/ondc diff --git a/pkg/plugin/implementation/router/testData/v2_bap_caller.yaml b/pkg/plugin/implementation/router/testData/v2_bap_caller.yaml new file mode 100644 index 0000000..8bf6fce --- /dev/null +++ b/pkg/plugin/implementation/router/testData/v2_bap_caller.yaml @@ -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 diff --git a/pkg/plugin/implementation/router/testData/v2_bpp_receiver.yaml b/pkg/plugin/implementation/router/testData/v2_bpp_receiver.yaml new file mode 100644 index 0000000..69b798d --- /dev/null +++ b/pkg/plugin/implementation/router/testData/v2_bpp_receiver.yaml @@ -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 diff --git a/pkg/plugin/implementation/router/testData/valid_all_routes.yaml b/pkg/plugin/implementation/router/testData/valid_all_routes.yaml index a818b92..f209a01 100644 --- a/pkg/plugin/implementation/router/testData/valid_all_routes.yaml +++ b/pkg/plugin/implementation/router/testData/valid_all_routes.yaml @@ -1,14 +1,14 @@ # testData/all_route_types.yaml routingRules: - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: url target: url: https://mock_gateway.com/v2/ondc endpoints: - search - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: bap target: url: https://mock_bpp.com/v2/ondc @@ -16,14 +16,14 @@ routingRules: - init - select - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: publisher target: publisherId: beckn_onix_topic endpoints: - confirm - domain: ONDC:TRV10 - version: 2.0.0 + version: 1.1.0 targetType: bap target: url: https://mock_bap_gateway.com/v2/ondc