updated router
This commit is contained in:
14
go.mod
14
go.mod
@@ -1,13 +1,8 @@
|
|||||||
module github.com/beckn/beckn-onix
|
module github.com/beckn/beckn-onix
|
||||||
|
|
||||||
go 1.23.4
|
go 1.24
|
||||||
|
|
||||||
toolchain go1.23.7
|
require golang.org/x/crypto v0.36.0
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
|
|
||||||
golang.org/x/crypto v0.36.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
@@ -15,10 +10,7 @@ require (
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03
|
||||||
github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03
|
|
||||||
golang.org/x/text v0.23.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,6 +1,4 @@
|
|||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -12,18 +10,14 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
|
|||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
|
||||||
github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY=
|
github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY=
|
||||||
github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY=
|
github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Route defines the structure for the Route returned
|
// Route defines the structure for the Route returned.
|
||||||
type Route struct {
|
type Route struct {
|
||||||
RoutingType string // "url" or "msgq"
|
TargetType string // "url" or "msgq"
|
||||||
TopicID string // For message queues
|
PublisherID string // For message queues
|
||||||
TargetURL string // For API calls
|
URL string // For API calls
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouterProvider initializes the a new Router instance with the given config
|
// RouterProvider initializes the a new Router instance with the given config.
|
||||||
type RouterProvider interface {
|
type RouterProvider interface {
|
||||||
New(ctx context.Context, config map[string]string) (Router, func() error, error)
|
New(ctx context.Context, config map[string]string) (Router, func() error, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router defines the interface for routing requests
|
// Router defines the interface for routing requests.
|
||||||
type Router interface {
|
type Router interface {
|
||||||
// Route determines the routing destination based on the request context.
|
// Route determines the routing destination based on the request context.
|
||||||
Route(ctx context.Context, url *url.URL, body []byte) (*Route, error)
|
Route(ctx context.Context, url *url.URL, body []byte) (*Route, error)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func (rp RouterProvider) New(ctx context.Context, config map[string]string) (def
|
|||||||
return nil, nil, errors.New("context cannot be nil")
|
return nil, nil, errors.New("context cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the routing_config key from the config map
|
// Parse the routingConfig key from the config map
|
||||||
routingConfig, ok := config["routingConfig"]
|
routingConfig, ok := config["routingConfig"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, errors.New("routingConfig is required in the configuration")
|
return nil, nil, errors.New("routingConfig is required in the configuration")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -12,42 +13,23 @@ import (
|
|||||||
func setupTestConfig(t *testing.T) string {
|
func setupTestConfig(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// Create a temporary directory for the routing rules
|
// Get project root (assuming testData is in project root)
|
||||||
configDir, err := os.MkdirTemp("", "routingRules")
|
_, filename, _, _ := runtime.Caller(0) // Path to plugin_test.go
|
||||||
|
projectRoot := filepath.Dir(filepath.Dir(filename)) // Move up from cmd/
|
||||||
|
yamlPath := filepath.Join(projectRoot, "testData", "bap_receiver.yaml")
|
||||||
|
|
||||||
|
// Copy to temp file (to test file loading logic)
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
tempPath := filepath.Join(tempDir, "routingRules.yaml")
|
||||||
|
content, err := os.ReadFile(yamlPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp directory: %v", err)
|
t.Fatalf("Failed to read test file: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(tempPath, content, 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to create temp config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define sample routing rules
|
return tempPath
|
||||||
rulesContent := `
|
|
||||||
routingRules:
|
|
||||||
- domain: "ONDC:TRV11"
|
|
||||||
version: "2.0.0"
|
|
||||||
routingType: "url"
|
|
||||||
target:
|
|
||||||
url: "https://services-backend/trv/v1"
|
|
||||||
endpoints:
|
|
||||||
- select
|
|
||||||
- init
|
|
||||||
- confirm
|
|
||||||
- status
|
|
||||||
|
|
||||||
- domain: "ONDC:TRV11"
|
|
||||||
version: "2.0.0"
|
|
||||||
routingType: "msgq"
|
|
||||||
target:
|
|
||||||
topic_id: "trv_topic_id1"
|
|
||||||
endpoints:
|
|
||||||
- search
|
|
||||||
`
|
|
||||||
|
|
||||||
// Write the routing rules to a file
|
|
||||||
rulesFilePath := filepath.Join(configDir, "routingRules.yaml")
|
|
||||||
if err := os.WriteFile(rulesFilePath, []byte(rulesContent), 0644); err != nil {
|
|
||||||
t.Fatalf("Failed to write routing rules file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rulesFilePath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRouterProviderSuccess tests the RouterProvider implementation for success cases.
|
// TestRouterProviderSuccess tests the RouterProvider implementation for success cases.
|
||||||
@@ -57,9 +39,10 @@ func TestRouterProviderSuccess(t *testing.T) {
|
|||||||
|
|
||||||
// Define test cases
|
// Define test cases
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
config map[string]string
|
config map[string]string
|
||||||
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Valid configuration",
|
name: "Valid configuration",
|
||||||
@@ -67,6 +50,7 @@ func TestRouterProviderSuccess(t *testing.T) {
|
|||||||
config: map[string]string{
|
config: map[string]string{
|
||||||
"routingConfig": rulesFilePath,
|
"routingConfig": rulesFilePath,
|
||||||
},
|
},
|
||||||
|
wantErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,14 +60,14 @@ func TestRouterProviderSuccess(t *testing.T) {
|
|||||||
router, _, err := provider.New(tt.ctx, tt.config)
|
router, _, err := provider.New(tt.ctx, tt.config)
|
||||||
|
|
||||||
// Ensure no error occurred
|
// Ensure no error occurred
|
||||||
if err != nil {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("New(%v, %v) error = %v, wantErr %v", tt.ctx, tt.config, err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the router and close function are not nil
|
// Ensure the router and close function are not nil
|
||||||
if router == nil {
|
if router == nil {
|
||||||
t.Error("expected a non-nil Router instance, got nil")
|
t.Errorf("New(%v, %v) = nil router, want non-nil", tt.ctx, tt.config)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -96,10 +80,10 @@ func TestRouterProviderFailure(t *testing.T) {
|
|||||||
|
|
||||||
// Define test cases
|
// Define test cases
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
config map[string]string
|
config map[string]string
|
||||||
expectedError string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Empty routing config path",
|
name: "Empty routing config path",
|
||||||
@@ -107,19 +91,19 @@ func TestRouterProviderFailure(t *testing.T) {
|
|||||||
config: map[string]string{
|
config: map[string]string{
|
||||||
"routingConfig": "",
|
"routingConfig": "",
|
||||||
},
|
},
|
||||||
expectedError: "failed to load routing rules: routingConfig path is empty",
|
wantErr: "failed to load routing rules: routingConfig path is empty",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Missing routing config key",
|
name: "Missing routing config key",
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
config: map[string]string{},
|
config: map[string]string{},
|
||||||
expectedError: "routingConfig is required in the configuration",
|
wantErr: "routingConfig is required in the configuration",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Nil context",
|
name: "Nil context",
|
||||||
ctx: nil,
|
ctx: nil,
|
||||||
config: map[string]string{"routingConfig": rulesFilePath},
|
config: map[string]string{"routingConfig": rulesFilePath},
|
||||||
expectedError: "context cannot be nil",
|
wantErr: "context cannot be nil",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +113,10 @@ func TestRouterProviderFailure(t *testing.T) {
|
|||||||
_, _, err := provider.New(tt.ctx, tt.config)
|
_, _, err := provider.New(tt.ctx, tt.config)
|
||||||
|
|
||||||
// Check for expected error
|
// Check for expected error
|
||||||
if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
|
if err == nil {
|
||||||
t.Errorf("expected error %q, got %v", tt.expectedError, err)
|
t.Errorf("New(%v, %v) = nil error, want error containing %q", tt.ctx, tt.config, tt.wantErr)
|
||||||
|
} else if !strings.Contains(err.Error(), tt.wantErr) {
|
||||||
|
t.Errorf("New(%v, %v) = %v, want error containing %q", tt.ctx, tt.config, err, tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,27 +24,34 @@ type routingConfig struct {
|
|||||||
RoutingRules []routingRule `yaml:"routingRules"`
|
RoutingRules []routingRule `yaml:"routingRules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router implements Router interface
|
// Router implements Router interface.
|
||||||
type Router struct {
|
type Router struct {
|
||||||
config *Config
|
rules map[string]map[string]map[string]*definition.Route // domain -> version -> endpoint -> route
|
||||||
rules []routingRule
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoutingRule represents a single routing rule.
|
// RoutingRule represents a single routing rule.
|
||||||
type routingRule struct {
|
type routingRule struct {
|
||||||
Domain string `yaml:"domain"`
|
Domain string `yaml:"domain"`
|
||||||
Version string `yaml:"version"`
|
Version string `yaml:"version"`
|
||||||
RoutingType string `yaml:"routingType"` // "url", "msgq", "bpp", or "bap"
|
TargetType string `yaml:"targetType"` // "url", "msgq", "bpp", or "bap"
|
||||||
Target target `yaml:"target,omitempty"`
|
Target target `yaml:"target,omitempty"`
|
||||||
Endpoints []string `yaml:"endpoints"`
|
Endpoints []string `yaml:"endpoints"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target contains destination-specific details.
|
// Target contains destination-specific details.
|
||||||
type target struct {
|
type target struct {
|
||||||
URL string `yaml:"url,omitempty"` // URL for "url" or gateway endpoint for "bpp"/"bap"
|
URL string `yaml:"url,omitempty"` // URL for "url" or gateway endpoint for "bpp"/"bap"
|
||||||
TopicID string `yaml:"topic_id,omitempty"` // For "msgq" type
|
PublisherID string `yaml:"publisherId,omitempty"` // For "msgq" type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TargetType defines possible target destinations.
|
||||||
|
const (
|
||||||
|
targetTypeURL = "url" // Route to a specific URL
|
||||||
|
targetTypeMSGQ = "msgq" // Route to a message queue
|
||||||
|
targetTypeBPP = "bpp" // Route to a BPP endpoint
|
||||||
|
targetTypeBAP = "bap" // Route to a BAP endpoint
|
||||||
|
)
|
||||||
|
|
||||||
// New initializes a new Router instance with the provided configuration.
|
// New initializes a new Router instance with the provided configuration.
|
||||||
// It loads and validates the routing rules from the specified YAML file.
|
// It loads and validates the routing rules from the specified YAML file.
|
||||||
// Returns an error if the configuration is invalid or the rules cannot be loaded.
|
// Returns an error if the configuration is invalid or the rules cannot be loaded.
|
||||||
@@ -54,24 +61,24 @@ func New(ctx context.Context, config *Config) (*Router, func() error, error) {
|
|||||||
return nil, nil, fmt.Errorf("config cannot be nil")
|
return nil, nil, fmt.Errorf("config cannot be nil")
|
||||||
}
|
}
|
||||||
router := &Router{
|
router := &Router{
|
||||||
config: config,
|
rules: make(map[string]map[string]map[string]*definition.Route),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load rules at bootup
|
// Load rules at bootup
|
||||||
if err := router.loadRules(); err != nil {
|
if err := router.loadRules(config.RoutingConfig); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to load routing rules: %w", err)
|
return nil, nil, fmt.Errorf("failed to load routing rules: %w", err)
|
||||||
}
|
}
|
||||||
return router, nil, nil
|
return router, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRules reads and parses routing rules from the YAML configuration file.
|
// LoadRules reads and parses routing rules from the YAML configuration file.
|
||||||
func (r *Router) loadRules() error {
|
func (r *Router) loadRules(configPath string) error {
|
||||||
if r.config.RoutingConfig == "" {
|
if configPath == "" {
|
||||||
return fmt.Errorf("routingConfig path is empty")
|
return fmt.Errorf("routingConfig path is empty")
|
||||||
}
|
}
|
||||||
data, err := os.ReadFile(r.config.RoutingConfig)
|
data, err := os.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading config file at %s: %w", r.config.RoutingConfig, err)
|
return fmt.Errorf("error reading config file at %s: %w", configPath, err)
|
||||||
}
|
}
|
||||||
var config routingConfig
|
var config routingConfig
|
||||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||||
@@ -82,36 +89,74 @@ func (r *Router) loadRules() error {
|
|||||||
if err := validateRules(config.RoutingRules); err != nil {
|
if err := validateRules(config.RoutingRules); err != nil {
|
||||||
return fmt.Errorf("invalid routing rules: %w", err)
|
return fmt.Errorf("invalid routing rules: %w", err)
|
||||||
}
|
}
|
||||||
r.rules = config.RoutingRules
|
// Build the optimized rule map
|
||||||
|
for _, rule := range config.RoutingRules {
|
||||||
|
// Initialize domain map if not exists
|
||||||
|
if _, ok := r.rules[rule.Domain]; !ok {
|
||||||
|
r.rules[rule.Domain] = make(map[string]map[string]*definition.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize version map if not exists
|
||||||
|
if _, ok := r.rules[rule.Domain][rule.Version]; !ok {
|
||||||
|
r.rules[rule.Domain][rule.Version] = make(map[string]*definition.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all endpoints for this rule
|
||||||
|
for _, endpoint := range rule.Endpoints {
|
||||||
|
var route *definition.Route
|
||||||
|
switch rule.TargetType {
|
||||||
|
case targetTypeMSGQ:
|
||||||
|
route = &definition.Route{
|
||||||
|
TargetType: rule.TargetType,
|
||||||
|
PublisherID: rule.Target.PublisherID,
|
||||||
|
}
|
||||||
|
case targetTypeURL:
|
||||||
|
route = &definition.Route{
|
||||||
|
TargetType: rule.TargetType,
|
||||||
|
URL: rule.Target.URL,
|
||||||
|
}
|
||||||
|
case targetTypeBPP, targetTypeBAP:
|
||||||
|
route = &definition.Route{
|
||||||
|
TargetType: rule.TargetType,
|
||||||
|
URL: rule.Target.URL, // Fallback URL if URI not provided in request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(r.rules)
|
||||||
|
|
||||||
|
r.rules[rule.Domain][rule.Version][endpoint] = route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 routingType are present
|
// Ensure domain, version, and TargetType are present
|
||||||
if rule.Domain == "" || rule.Version == "" || rule.RoutingType == "" {
|
if rule.Domain == "" || rule.Version == "" || rule.TargetType == "" {
|
||||||
return fmt.Errorf("invalid rule: domain, version, and routingType are required")
|
return fmt.Errorf("invalid rule: domain, version, and targetType are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate based on routingType
|
// Validate based on TargetType
|
||||||
switch rule.RoutingType {
|
switch rule.TargetType {
|
||||||
case "url":
|
case targetTypeURL:
|
||||||
if rule.Target.URL == "" {
|
if rule.Target.URL == "" {
|
||||||
return fmt.Errorf("invalid rule: url is required for routingType 'url'")
|
return fmt.Errorf("invalid rule: url is required for targetType 'url'")
|
||||||
}
|
}
|
||||||
if _, err := url.ParseRequestURI(rule.Target.URL); err != nil {
|
if _, err := url.ParseRequestURI(rule.Target.URL); err != nil {
|
||||||
return fmt.Errorf("invalid URL in rule: %w", err)
|
return fmt.Errorf("invalid URL in rule: %w", err)
|
||||||
}
|
}
|
||||||
case "msgq":
|
case targetTypeMSGQ:
|
||||||
if rule.Target.TopicID == "" {
|
if rule.Target.PublisherID == "" {
|
||||||
return fmt.Errorf("invalid rule: topicId is required for routingType 'msgq'")
|
return fmt.Errorf("invalid rule: publisherID is required for targetType 'msgq'")
|
||||||
}
|
}
|
||||||
case "bpp", "bap":
|
case targetTypeBPP, targetTypeBAP:
|
||||||
// No target validation needed for bpp/bap, as they use URIs from the request body
|
// No target validation needed for bpp/bap, as they use URIs from the request body
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid rule: unknown routingType '%s'", rule.RoutingType)
|
return fmt.Errorf("invalid rule: unknown targetType '%s'", rule.TargetType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -125,8 +170,8 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definit
|
|||||||
Context struct {
|
Context struct {
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
BppURI string `json:"bpp_uri,omitempty"`
|
BPPURI string `json:"bpp_uri,omitempty"`
|
||||||
BapURI string `json:"bap_uri,omitempty"`
|
BAPURI string `json:"bap_uri,omitempty"`
|
||||||
} `json:"context"`
|
} `json:"context"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(body, &requestBody); err != nil {
|
if err := json.Unmarshal(body, &requestBody); err != nil {
|
||||||
@@ -136,69 +181,52 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definit
|
|||||||
// Extract the endpoint from the URL
|
// Extract the endpoint from the URL
|
||||||
endpoint := path.Base(url.Path)
|
endpoint := path.Base(url.Path)
|
||||||
|
|
||||||
// Collect all matching rules for the domain and version
|
// Lookup route in the optimized map
|
||||||
matchingRules := r.getMatchingRules(requestBody.Context.Domain, requestBody.Context.Version)
|
domainRules, ok := r.rules[requestBody.Context.Domain]
|
||||||
|
if !ok {
|
||||||
// If no matching rules are found, return an error
|
return nil, fmt.Errorf("no routing rules found for domain %s", requestBody.Context.Domain)
|
||||||
if len(matchingRules) == 0 {
|
|
||||||
return nil, fmt.Errorf("no matching routing rule found for domain %s and version %s", requestBody.Context.Domain, requestBody.Context.Version)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match the rule
|
versionRules, ok := domainRules[requestBody.Context.Version]
|
||||||
for _, rule := range matchingRules {
|
if !ok {
|
||||||
for _, ep := range rule.Endpoints {
|
return nil, fmt.Errorf("no routing rules found for domain %s version %s", requestBody.Context.Domain, requestBody.Context.Version)
|
||||||
if strings.EqualFold(ep, endpoint) {
|
}
|
||||||
switch rule.RoutingType {
|
|
||||||
case "msgq":
|
route, ok := versionRules[endpoint]
|
||||||
return &definition.Route{
|
if !ok {
|
||||||
RoutingType: rule.RoutingType,
|
return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s in routing config",
|
||||||
TopicID: rule.Target.TopicID,
|
endpoint, requestBody.Context.Domain, requestBody.Context.Version)
|
||||||
}, nil
|
}
|
||||||
case "url":
|
|
||||||
return &definition.Route{
|
// Handle BPP/BAP routing with request URIs
|
||||||
RoutingType: rule.RoutingType,
|
switch route.TargetType {
|
||||||
TargetURL: rule.Target.URL,
|
case targetTypeBPP:
|
||||||
}, nil
|
uri := strings.TrimSpace(requestBody.Context.BPPURI)
|
||||||
case "bpp":
|
target := strings.TrimSpace(route.URL)
|
||||||
return handleRouting(rule, requestBody.Context.BppURI, endpoint, "bpp")
|
if len(uri) != 0 {
|
||||||
case "bap":
|
target = uri
|
||||||
return handleRouting(rule, requestBody.Context.BapURI, endpoint, "bap")
|
}
|
||||||
default:
|
if len(target) == 0 {
|
||||||
return nil, fmt.Errorf("unsupported routingType: %s", rule.RoutingType)
|
return nil, fmt.Errorf("could not determine destination for endpoint '%s': neither request contained a BPP URI nor was a default URL configured in routing rules", endpoint)
|
||||||
}
|
}
|
||||||
}
|
route = &definition.Route{
|
||||||
|
TargetType: route.TargetType,
|
||||||
|
URL: target,
|
||||||
|
}
|
||||||
|
case targetTypeBAP:
|
||||||
|
uri := strings.TrimSpace(requestBody.Context.BAPURI)
|
||||||
|
target := strings.TrimSpace(route.URL)
|
||||||
|
if len(uri) != 0 {
|
||||||
|
target = uri
|
||||||
|
}
|
||||||
|
if len(target) == 0 {
|
||||||
|
return nil, fmt.Errorf("could not determine destination for endpoint '%s': neither request contained a BAP URI nor was a default URL configured in routing rules", endpoint)
|
||||||
|
}
|
||||||
|
route = &definition.Route{
|
||||||
|
TargetType: route.TargetType,
|
||||||
|
URL: target,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If domain and version match but endpoint is not found, return an error
|
return route, nil
|
||||||
return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s", endpoint, requestBody.Context.Domain, requestBody.Context.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMatchingRules returns all rules that match the given domain and version
|
|
||||||
func (r *Router) getMatchingRules(domain, version string) []routingRule {
|
|
||||||
var matchingRules []routingRule
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
if rule.Domain == domain && rule.Version == version {
|
|
||||||
matchingRules = append(matchingRules, rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matchingRules
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleRouting handles routing for bap and bpp routing type
|
|
||||||
func handleRouting(rule routingRule, uri, endpoint string, routingType string) (*definition.Route, error) {
|
|
||||||
if uri == "" {
|
|
||||||
if rule.Target.URL != "" {
|
|
||||||
return &definition.Route{
|
|
||||||
RoutingType: routingType,
|
|
||||||
TargetURL: rule.Target.URL,
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("no target URI or URL found for %s routing type and %s endpoint", routingType, endpoint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &definition.Route{
|
|
||||||
RoutingType: routingType,
|
|
||||||
TargetURL: uri,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,35 +15,19 @@ var testData embed.FS
|
|||||||
|
|
||||||
func setupTestConfig(t *testing.T, yamlFileName string) string {
|
func setupTestConfig(t *testing.T, yamlFileName string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
configDir := t.TempDir()
|
||||||
|
|
||||||
// Create a temporary directory for the routing rules
|
content, err := testData.ReadFile("testData/" + yamlFileName)
|
||||||
configDir, err := os.MkdirTemp("", "routing_rules")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp directory: %v", err)
|
t.Fatalf("ReadFile() err = %v, want nil", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the YAML file content
|
rulesPath := filepath.Join(configDir, "routing_rules.yaml")
|
||||||
yamlContent := readYAMLFile(t, yamlFileName)
|
if err := os.WriteFile(rulesPath, content, 0644); err != nil {
|
||||||
|
t.Fatalf("WriteFile() err = %v, want nil", err)
|
||||||
// Write the routing rules to a file
|
|
||||||
rulesFilePath := filepath.Join(configDir, "routing_rules.yaml")
|
|
||||||
if err := os.WriteFile(rulesFilePath, []byte(yamlContent), 0644); err != nil {
|
|
||||||
t.Fatalf("Failed to write routing rules file: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rulesFilePath
|
return rulesPath
|
||||||
}
|
|
||||||
|
|
||||||
func readYAMLFile(t *testing.T, fileName string) string {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
// Read the YAML file
|
|
||||||
content, err := testData.ReadFile("testData/" + fileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to read YAML file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupRouter is a helper function to create router instance.
|
// setupRouter is a helper function to create router instance.
|
||||||
@@ -78,35 +62,35 @@ func TestNew(t *testing.T) {
|
|||||||
|
|
||||||
// Define test cases
|
// Define test cases
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
config *Config
|
config *Config
|
||||||
expectedError string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Valid configuration",
|
name: "Valid configuration",
|
||||||
config: &Config{
|
config: &Config{
|
||||||
RoutingConfig: rulesFilePath,
|
RoutingConfig: rulesFilePath,
|
||||||
},
|
},
|
||||||
expectedError: "",
|
wantErr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Empty config",
|
name: "Empty config",
|
||||||
config: nil,
|
config: nil,
|
||||||
expectedError: "config cannot be nil",
|
wantErr: "config cannot be nil",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Empty routing config path",
|
name: "Empty routing config path",
|
||||||
config: &Config{
|
config: &Config{
|
||||||
RoutingConfig: "",
|
RoutingConfig: "",
|
||||||
},
|
},
|
||||||
expectedError: "routingConfig path is empty",
|
wantErr: "routingConfig path is empty",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Routing config file does not exist",
|
name: "Routing config file does not exist",
|
||||||
config: &Config{
|
config: &Config{
|
||||||
RoutingConfig: "/nonexistent/path/to/rules.yaml",
|
RoutingConfig: "/nonexistent/path/to/rules.yaml",
|
||||||
},
|
},
|
||||||
expectedError: "error reading config file",
|
wantErr: "error reading config file",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,22 +99,22 @@ func TestNew(t *testing.T) {
|
|||||||
router, _, err := New(ctx, tt.config)
|
router, _, err := New(ctx, tt.config)
|
||||||
|
|
||||||
// Check for expected error
|
// Check for expected error
|
||||||
if tt.expectedError != "" {
|
if tt.wantErr != "" {
|
||||||
if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
|
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
|
||||||
t.Errorf("expected error %q, got %v", tt.expectedError, err)
|
t.Errorf("New(%v) = %v, want error containing %q", tt.config, err, tt.wantErr)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure no error occurred
|
// Ensure no error occurred
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("New(%v) = %v, want nil error", tt.config, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the router and close function are not nil
|
// Ensure the router and close function are not nil
|
||||||
if router == nil {
|
if router == nil {
|
||||||
t.Error("expected a non-nil Router instance, got nil")
|
t.Errorf("New(%v, %v) = nil router, want non-nil", ctx, tt.config)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -148,9 +132,9 @@ func TestValidateRulesSuccess(t *testing.T) {
|
|||||||
name: "Valid rules with url routing",
|
name: "Valid rules with url routing",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "url",
|
TargetType: "url",
|
||||||
Target: target{
|
Target: target{
|
||||||
URL: "https://example.com/api",
|
URL: "https://example.com/api",
|
||||||
},
|
},
|
||||||
@@ -162,11 +146,11 @@ func TestValidateRulesSuccess(t *testing.T) {
|
|||||||
name: "Valid rules with msgq routing",
|
name: "Valid rules with msgq routing",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "msgq",
|
TargetType: "msgq",
|
||||||
Target: target{
|
Target: target{
|
||||||
TopicID: "example_topic",
|
PublisherID: "example_topic",
|
||||||
},
|
},
|
||||||
Endpoints: []string{"on_search", "on_select"},
|
Endpoints: []string{"on_search", "on_select"},
|
||||||
},
|
},
|
||||||
@@ -176,9 +160,9 @@ func TestValidateRulesSuccess(t *testing.T) {
|
|||||||
name: "Valid rules with bpp routing to gateway",
|
name: "Valid rules with bpp routing to gateway",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "bpp",
|
TargetType: "bpp",
|
||||||
Target: target{
|
Target: target{
|
||||||
URL: "https://mock_gateway.com/api",
|
URL: "https://mock_gateway.com/api",
|
||||||
},
|
},
|
||||||
@@ -190,10 +174,10 @@ func TestValidateRulesSuccess(t *testing.T) {
|
|||||||
name: "Valid rules with bpp routing",
|
name: "Valid rules with bpp routing",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "bpp",
|
TargetType: "bpp",
|
||||||
Endpoints: []string{"select"},
|
Endpoints: []string{"select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -201,10 +185,10 @@ func TestValidateRulesSuccess(t *testing.T) {
|
|||||||
name: "Valid rules with bap routing",
|
name: "Valid rules with bap routing",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "bap",
|
TargetType: "bap",
|
||||||
Endpoints: []string{"select"},
|
Endpoints: []string{"select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -214,7 +198,7 @@ func TestValidateRulesSuccess(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
err := validateRules(tt.rules)
|
err := validateRules(tt.rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("validateRules(%v) = %v, want nil error", tt.rules, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -223,40 +207,40 @@ func TestValidateRulesSuccess(t *testing.T) {
|
|||||||
// TestValidateRulesFailure tests the validate function for failure cases.
|
// TestValidateRulesFailure tests the validate function for failure cases.
|
||||||
func TestValidateRulesFailure(t *testing.T) {
|
func TestValidateRulesFailure(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
rules []routingRule
|
rules []routingRule
|
||||||
expectedErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Missing domain",
|
name: "Missing domain",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "url",
|
TargetType: "url",
|
||||||
Target: target{
|
Target: target{
|
||||||
URL: "https://example.com/api",
|
URL: "https://example.com/api",
|
||||||
},
|
},
|
||||||
Endpoints: []string{"search", "select"},
|
Endpoints: []string{"search", "select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: "invalid rule: domain, version, and routingType are required",
|
wantErr: "invalid rule: domain, version, and targetType are required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Missing version",
|
name: "Missing version",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
RoutingType: "url",
|
TargetType: "url",
|
||||||
Target: target{
|
Target: target{
|
||||||
URL: "https://example.com/api",
|
URL: "https://example.com/api",
|
||||||
},
|
},
|
||||||
Endpoints: []string{"search", "select"},
|
Endpoints: []string{"search", "select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: "invalid rule: domain, version, and routingType are required",
|
wantErr: "invalid rule: domain, version, and targetType are required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Missing routingType",
|
name: "Missing targetType",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
@@ -267,62 +251,60 @@ func TestValidateRulesFailure(t *testing.T) {
|
|||||||
Endpoints: []string{"search", "select"},
|
Endpoints: []string{"search", "select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: "invalid rule: domain, version, and routingType are required",
|
wantErr: "invalid rule: domain, version, and targetType are required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid routingType",
|
name: "Invalid targetType",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "invalid",
|
TargetType: "invalid",
|
||||||
Target: target{
|
Target: target{
|
||||||
URL: "https://example.com/api",
|
URL: "https://example.com/api",
|
||||||
},
|
},
|
||||||
Endpoints: []string{"search", "select"},
|
Endpoints: []string{"search", "select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: "invalid rule: unknown routingType 'invalid'",
|
wantErr: "invalid rule: unknown targetType 'invalid'",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Missing url for routingType: url",
|
name: "Missing url for targetType: url",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "url",
|
TargetType: "url",
|
||||||
Target: target{
|
Target: target{
|
||||||
// URL is missing
|
// URL is missing
|
||||||
},
|
},
|
||||||
Endpoints: []string{"search", "select"},
|
Endpoints: []string{"search", "select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: "invalid rule: url is required for routingType 'url'",
|
wantErr: "invalid rule: url is required for targetType 'url'",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Missing topic_id for routingType: msgq",
|
name: "Missing topic_id for targetType: msgq",
|
||||||
rules: []routingRule{
|
rules: []routingRule{
|
||||||
{
|
{
|
||||||
Domain: "retail",
|
Domain: "retail",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
RoutingType: "msgq",
|
TargetType: "msgq",
|
||||||
Target: target{
|
Target: target{
|
||||||
// TopicID is missing
|
// PublisherID is missing
|
||||||
},
|
},
|
||||||
Endpoints: []string{"search", "select"},
|
Endpoints: []string{"search", "select"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: "invalid rule: topicId is required for routingType 'msgq'",
|
wantErr: "invalid rule: publisherID is required for targetType 'msgq'",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
err := validateRules(tt.rules)
|
err := validateRules(tt.rules)
|
||||||
if err == nil {
|
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
|
||||||
t.Errorf("expected error: %v, got nil", tt.expectedErr)
|
t.Errorf("validateRules(%v) = %v, want error containing %q", tt.rules, err, tt.wantErr)
|
||||||
} else if err.Error() != tt.expectedErr {
|
|
||||||
t.Errorf("expected error: %v, got: %v", tt.expectedErr, err)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -387,7 +369,7 @@ func TestRouteSuccess(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure no error occurred
|
// Ensure no error occurred
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("router.Route(%v, %v, %v) = %v, want nil error", ctx, parsedURL, []byte(tt.body), err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -399,39 +381,39 @@ func TestRouteFailure(t *testing.T) {
|
|||||||
|
|
||||||
// Define failure test cases
|
// Define failure test cases
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
configFile string
|
configFile string
|
||||||
url string
|
url string
|
||||||
body string
|
body string
|
||||||
expectedError string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
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": "2.0.0"}}`,
|
||||||
expectedError: "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 2.0.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": "2.0.0"}}`,
|
||||||
expectedError: "no matching routing rule found for domain ONDC:SRV11 and version 2.0.0",
|
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": "2.0.0"}}`,
|
||||||
expectedError: "no target URI or URL found for bap routing type and on_search endpoint",
|
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": "2.0.0"}}`,
|
||||||
expectedError: "no target URI or URL found for bpp routing type and select endpoint",
|
wantErr: "could not determine destination for endpoint 'select': neither request contained a BPP URI nor was a default URL configured in routing rules",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,8 +426,8 @@ func TestRouteFailure(t *testing.T) {
|
|||||||
_, err := router.Route(ctx, parsedURL, []byte(tt.body))
|
_, err := router.Route(ctx, parsedURL, []byte(tt.body))
|
||||||
|
|
||||||
// Check for expected error
|
// Check for expected error
|
||||||
if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
|
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
|
||||||
t.Errorf("expected error %q, got %v", tt.expectedError, err)
|
t.Errorf("Route(%q, %q) = %v, want error containing %q", tt.url, tt.body, err, tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
routingRules:
|
routingRules:
|
||||||
- domain: "ONDC:TRV10"
|
- domain: "ONDC:TRV10"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "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: "2.0.0"
|
||||||
routingType: "bpp"
|
targetType: "bpp"
|
||||||
endpoints:
|
endpoints:
|
||||||
- select
|
- select
|
||||||
- init
|
- init
|
||||||
@@ -17,7 +17,7 @@ routingRules:
|
|||||||
- cancel
|
- cancel
|
||||||
- domain: "ONDC:TRV12"
|
- domain: "ONDC:TRV12"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "bpp"
|
targetType: "bpp"
|
||||||
endpoints:
|
endpoints:
|
||||||
- select
|
- select
|
||||||
- init
|
- init
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
routingRules:
|
routingRules:
|
||||||
- domain: "ONDC:TRV10"
|
- domain: "ONDC:TRV10"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "url"
|
targetType: "url"
|
||||||
target:
|
target:
|
||||||
url: "https://services-backend/trv/v1"
|
url: "https://services-backend/trv/v1"
|
||||||
endpoints:
|
endpoints:
|
||||||
@@ -13,8 +13,8 @@ routingRules:
|
|||||||
- on_cancel
|
- on_cancel
|
||||||
- domain: "ONDC:TRV10"
|
- domain: "ONDC:TRV10"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "msgq"
|
targetType: "msgq"
|
||||||
target:
|
target:
|
||||||
topic_id: "trv_topic_id1"
|
publisherId: "trv_topic_id1"
|
||||||
endpoints:
|
endpoints:
|
||||||
- on_search
|
- on_search
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
routingRules:
|
routingRules:
|
||||||
- domain: "ONDC:TRV10"
|
- domain: "ONDC:TRV10"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "bap"
|
targetType: "bap"
|
||||||
endpoints:
|
endpoints:
|
||||||
- on_search
|
- on_search
|
||||||
- on_select
|
- on_select
|
||||||
@@ -12,7 +12,7 @@ routingRules:
|
|||||||
- on_cancel
|
- on_cancel
|
||||||
- domain: "ONDC:TRV11"
|
- domain: "ONDC:TRV11"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "bap"
|
targetType: "bap"
|
||||||
endpoints:
|
endpoints:
|
||||||
- on_search
|
- on_search
|
||||||
- on_select
|
- on_select
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
routingRules:
|
routingRules:
|
||||||
- domain: "ONDC:TRV10"
|
- domain: "ONDC:TRV10"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "url"
|
targetType: "url"
|
||||||
target:
|
target:
|
||||||
url: "https://services-backend/trv/v1"
|
url: "https://services-backend/trv/v1"
|
||||||
endpoints:
|
endpoints:
|
||||||
@@ -13,15 +13,15 @@ routingRules:
|
|||||||
|
|
||||||
- domain: "ONDC:TRV10"
|
- domain: "ONDC:TRV10"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "msgq"
|
targetType: "msgq"
|
||||||
target:
|
target:
|
||||||
topic_id: "trv_topic_id1"
|
publisherId: "trv_topic_id1"
|
||||||
endpoints:
|
endpoints:
|
||||||
- search
|
- search
|
||||||
|
|
||||||
- domain: "ONDC:TRV11"
|
- domain: "ONDC:TRV11"
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
routingType: "url"
|
targetType: "url"
|
||||||
target:
|
target:
|
||||||
url: "https://services-backend/trv/v1"
|
url: "https://services-backend/trv/v1"
|
||||||
endpoints:
|
endpoints:
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type Config struct {
|
|||||||
Decrypter PluginConfig `yaml:"decrypter"`
|
Decrypter PluginConfig `yaml:"decrypter"`
|
||||||
Encrypter PluginConfig `yaml:"encrypter"`
|
Encrypter PluginConfig `yaml:"encrypter"`
|
||||||
Publisher PluginConfig `yaml:"publisher"`
|
Publisher PluginConfig `yaml:"publisher"`
|
||||||
Router PluginConfig `yaml:"router"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginConfig represents configuration details for a plugin.
|
// PluginConfig represents configuration details for a plugin.
|
||||||
@@ -34,7 +33,6 @@ type Manager struct {
|
|||||||
dp definition.DecrypterProvider
|
dp definition.DecrypterProvider
|
||||||
ep definition.EncrypterProvider
|
ep definition.EncrypterProvider
|
||||||
pb definition.PublisherProvider
|
pb definition.PublisherProvider
|
||||||
rp definition.RouterProvider
|
|
||||||
cfg *Config
|
cfg *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +72,7 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) {
|
|||||||
return nil, fmt.Errorf("failed to load encryption plugin: %w", err)
|
return nil, fmt.Errorf("failed to load encryption plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load router plugin.
|
return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, cfg: cfg}, nil
|
||||||
rp, err := provider[definition.RouterProvider](cfg.Root, cfg.Router.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load encryption plugin: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, rp: rp, cfg: cfg}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// provider loads a plugin dynamically and retrieves its provider instance.
|
// provider loads a plugin dynamically and retrieves its provider instance.
|
||||||
@@ -177,17 +169,3 @@ func (m *Manager) Publisher(ctx context.Context) (definition.Publisher, error) {
|
|||||||
}
|
}
|
||||||
return publisher, nil
|
return publisher, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router retrieves the router plugin instances.
|
|
||||||
func (m *Manager) Router(ctx context.Context) (definition.Router, func() error, error) {
|
|
||||||
if m.rp == nil {
|
|
||||||
return nil, nil, fmt.Errorf("router plugin provider not loaded")
|
|
||||||
|
|
||||||
}
|
|
||||||
schemaValidator, close, err := m.rp.New(ctx, m.cfg.Router.Config)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
return nil, nil, fmt.Errorf("failed to initialize schema validator: %v", err)
|
|
||||||
}
|
|
||||||
return schemaValidator, close, nil
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user