105 lines
2.9 KiB
Go
105 lines
2.9 KiB
Go
package routing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// RuleStore is the persistence interface for routing rules.
|
|
// The MemStore implements it for tests; PgStore implements it for production.
|
|
type RuleStore interface {
|
|
// ListActive returns all enabled rules for tenantID, sorted by priority ASC.
|
|
ListActive(ctx context.Context, tenantID string) ([]RoutingRule, error)
|
|
// Get returns a single rule by id, scoped to tenantID.
|
|
Get(ctx context.Context, id, tenantID string) (RoutingRule, error)
|
|
// Create persists a new rule and returns the created record.
|
|
Create(ctx context.Context, rule RoutingRule) (RoutingRule, error)
|
|
// Update replaces an existing rule and returns the updated record.
|
|
Update(ctx context.Context, rule RoutingRule) (RoutingRule, error)
|
|
// Delete permanently removes a rule. Only affects the given tenantID.
|
|
Delete(ctx context.Context, id, tenantID string) error
|
|
}
|
|
|
|
// ErrNotFound is returned by Get/Update/Delete when the rule does not exist.
|
|
var ErrNotFound = fmt.Errorf("routing rule not found")
|
|
|
|
// ─── MemStore — in-memory implementation (tests & dev) ──────────────────────
|
|
|
|
// MemStore is a thread-safe in-memory RuleStore for unit tests.
|
|
type MemStore struct {
|
|
mu sync.RWMutex
|
|
rules map[string]RoutingRule // id → rule
|
|
seq int
|
|
}
|
|
|
|
// NewMemStore creates an empty MemStore.
|
|
func NewMemStore() *MemStore {
|
|
return &MemStore{rules: make(map[string]RoutingRule)}
|
|
}
|
|
|
|
func (m *MemStore) ListActive(_ context.Context, tenantID string) ([]RoutingRule, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var out []RoutingRule
|
|
for _, r := range m.rules {
|
|
if r.TenantID == tenantID && r.IsEnabled {
|
|
out = append(out, r)
|
|
}
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Priority < out[j].Priority })
|
|
return out, nil
|
|
}
|
|
|
|
func (m *MemStore) Get(_ context.Context, id, tenantID string) (RoutingRule, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
r, ok := m.rules[id]
|
|
if !ok || r.TenantID != tenantID {
|
|
return RoutingRule{}, ErrNotFound
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func (m *MemStore) Create(_ context.Context, rule RoutingRule) (RoutingRule, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.seq++
|
|
rule.ID = fmt.Sprintf("mem-%d", m.seq)
|
|
rule.CreatedAt = time.Now()
|
|
rule.UpdatedAt = time.Now()
|
|
m.rules[rule.ID] = rule
|
|
return rule, nil
|
|
}
|
|
|
|
func (m *MemStore) Update(_ context.Context, rule RoutingRule) (RoutingRule, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
existing, ok := m.rules[rule.ID]
|
|
if !ok || existing.TenantID != rule.TenantID {
|
|
return RoutingRule{}, ErrNotFound
|
|
}
|
|
rule.CreatedAt = existing.CreatedAt
|
|
rule.UpdatedAt = time.Now()
|
|
m.rules[rule.ID] = rule
|
|
return rule, nil
|
|
}
|
|
|
|
func (m *MemStore) Delete(_ context.Context, id, tenantID string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
r, ok := m.rules[id]
|
|
if !ok || r.TenantID != tenantID {
|
|
return ErrNotFound
|
|
}
|
|
delete(m.rules, id)
|
|
return nil
|
|
}
|