From 0bb268054c6b70d89a02044f278d703341324a08 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Tue, 24 Mar 2026 00:59:44 +0000 Subject: [PATCH] feat: add Provider interface and ProviderRegistry --- providers/provider.go | 57 ++++++++++++++++++++++++++++++++++++++ providers/provider_test.go | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 providers/provider.go create mode 100644 providers/provider_test.go diff --git a/providers/provider.go b/providers/provider.go new file mode 100644 index 0000000..4249bff --- /dev/null +++ b/providers/provider.go @@ -0,0 +1,57 @@ +package providers + +import ( + "fmt" + "sort" +) + +// Provider is the core abstraction for deploying database infrastructure. +// Phase 2a defines a minimal interface (Name, ValidateVersion, DefaultPorts). +// Phase 2b will add CreateSandbox, Start, Stop, Destroy, HealthCheck when +// ProxySQL and other providers need them. +type Provider interface { + Name() string + ValidateVersion(version string) error + DefaultPorts() PortRange +} + +type PortRange struct { + BasePort int + PortsPerInstance int +} + +type Registry struct { + providers map[string]Provider +} + +func NewRegistry() *Registry { + return &Registry{providers: make(map[string]Provider)} +} + +func (r *Registry) Register(p Provider) error { + name := p.Name() + if _, exists := r.providers[name]; exists { + return fmt.Errorf("provider %q already registered", name) + } + r.providers[name] = p + return nil +} + +func (r *Registry) Get(name string) (Provider, error) { + p, exists := r.providers[name] + if !exists { + return nil, fmt.Errorf("provider %q not found", name) + } + return p, nil +} + +func (r *Registry) List() []string { + names := make([]string, 0, len(r.providers)) + for name := range r.providers { + names = append(names, name) + } + sort.Strings(names) + return names +} + +var DefaultRegistry = NewRegistry() diff --git a/providers/provider_test.go b/providers/provider_test.go new file mode 100644 index 0000000..d1ca46a --- /dev/null +++ b/providers/provider_test.go @@ -0,0 +1,53 @@ +package providers + +import "testing" + +type mockProvider struct{ name string } + +func (m *mockProvider) Name() string { return m.name } +func (m *mockProvider) ValidateVersion(version string) error { return nil } +func (m *mockProvider) DefaultPorts() PortRange { return PortRange{BasePort: 9999, PortsPerInstance: 1} } + +func TestRegistryRegisterAndGet(t *testing.T) { + reg := NewRegistry() + mock := &mockProvider{name: "test"} + if err := reg.Register(mock); err != nil { + t.Fatalf("Register failed: %v", err) + } + p, err := reg.Get("test") + if err != nil { + t.Fatalf("Get failed: %v", err) + } + if p.Name() != "test" { + t.Errorf("expected name 'test', got %q", p.Name()) + } +} + +func TestRegistryDuplicateRegister(t *testing.T) { + reg := NewRegistry() + mock := &mockProvider{name: "test"} + _ = reg.Register(mock) + if err := reg.Register(mock); err == nil { + t.Fatal("expected error on duplicate register") + } +} + +func TestRegistryGetNotFound(t *testing.T) { + reg := NewRegistry() + if _, err := reg.Get("nonexistent"); err == nil { + t.Fatal("expected error on missing provider") + } +} + +func TestRegistryList(t *testing.T) { + reg := NewRegistry() + _ = reg.Register(&mockProvider{name: "b"}) + _ = reg.Register(&mockProvider{name: "a"}) + names := reg.List() + if len(names) != 2 { + t.Errorf("expected 2 providers, got %d", len(names)) + } + if names[0] != "a" || names[1] != "b" { + t.Errorf("expected sorted [a b], got %v", names) + } +}