From c5856133c8f12e3fdc76de0fc0482bf072580252 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 21 Apr 2026 22:58:44 +0300 Subject: t7 centralize provider registries --- internal/tts/registry.go | 89 +++++++++++++++++++++++++++++++++++++++++++ internal/tts/registry_test.go | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 internal/tts/registry.go create mode 100644 internal/tts/registry_test.go (limited to 'internal/tts') diff --git a/internal/tts/registry.go b/internal/tts/registry.go new file mode 100644 index 0000000..0b38a5e --- /dev/null +++ b/internal/tts/registry.go @@ -0,0 +1,89 @@ +package tts + +import ( + "fmt" + "reflect" + + "codeberg.org/snonux/comicforge/internal/provider" +) + +// Config exposes the configured TTS provider settings. +type Config interface { + provider.TTSConfig + GoogleAPIKey() string + TTSModel() string +} + +type registryConfig struct { + Config + voice string +} + +// Registry resolves provider names to factories. +type Registry struct { + *provider.Registry[provider.TTSProvider, registryConfig] +} + +// NewRegistry creates an empty TTS-provider registry. +func NewRegistry() *Registry { + return &Registry{ + Registry: provider.NewRegistry[provider.TTSProvider, registryConfig](), + } +} + +// DefaultRegistry returns the built-in TTS provider registry. +func DefaultRegistry() *Registry { + registry := NewRegistry() + registry.Register(provider.Gemini, func(cfg registryConfig) (provider.TTSProvider, error) { + geminiProvider := NewGeminiProvider(&GeminiConfig{ + APIKey: cfg.GoogleAPIKey(), + Model: cfg.TTSModel(), + Voice: cfg.voice, + }) + if err := geminiProvider.IsAvailable(); err != nil { + return nil, err + } + return geminiProvider, nil + }) + registry.Register(provider.OpenAI, func(registryConfig) (provider.TTSProvider, error) { + return nil, fmt.Errorf("tts provider %q is not implemented", provider.OpenAI) + }) + return registry +} + +// Has reports whether a factory is registered for name. +func (r *Registry) Has(name string) bool { + if r == nil || r.Registry == nil { + return false + } + _, ok := r.Resolve(name) + return ok +} + +// NewFromConfig resolves the provider name from cfg and constructs it. +func (r *Registry) NewFromConfig(cfg Config, voice string) (provider.TTSProvider, error) { + if r == nil || r.Registry == nil { + return nil, fmt.Errorf("tts registry is required") + } + if isNilValue(cfg) { + return nil, fmt.Errorf("tts config is required") + } + return r.New(cfg.TTSProviderName(), registryConfig{ + Config: cfg, + voice: voice, + }) +} + +func isNilValue[T any](value T) bool { + v := reflect.ValueOf(value) + if !v.IsValid() { + return true + } + + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: + return v.IsNil() + default: + return false + } +} diff --git a/internal/tts/registry_test.go b/internal/tts/registry_test.go new file mode 100644 index 0000000..f53fa17 --- /dev/null +++ b/internal/tts/registry_test.go @@ -0,0 +1,66 @@ +package tts + +import ( + "errors" + "strings" + "testing" + + "codeberg.org/snonux/comicforge/internal/provider" +) + +func TestDefaultRegistryNewFromConfig(t *testing.T) { + t.Parallel() + + registry := DefaultRegistry() + gotProvider, err := registry.NewFromConfig(fakeConfig{ + name: provider.Gemini, + apiKey: "test-key", + model: "gemini-2.5-flash-preview-tts", + }, "Aoede") + if err != nil { + t.Fatalf("NewFromConfig() error = %v", err) + } + if got, want := gotProvider.Name(), provider.Gemini; got != want { + t.Fatalf("provider.Name() = %q, want %q", got, want) + } +} + +func TestDefaultRegistryUnsupportedProvider(t *testing.T) { + t.Parallel() + + registry := DefaultRegistry() + _, err := registry.NewFromConfig(fakeConfig{ + name: provider.OpenAI, + apiKey: "test-key", + model: "unused", + }, "Aoede") + if err == nil { + t.Fatal("expected unsupported provider error") + } + if !strings.Contains(err.Error(), "tts provider \"openai\" is not implemented") { + t.Fatalf("error = %v, want unsupported provider error", err) + } +} + +func TestRegistryUnknownProvider(t *testing.T) { + t.Parallel() + + registry := DefaultRegistry() + _, err := registry.New("missing", registryConfig{Config: fakeConfig{}}) + if err == nil { + t.Fatal("expected unknown provider error") + } + if !errors.Is(err, provider.ErrUnknownProvider) { + t.Fatalf("error = %v, want ErrUnknownProvider", err) + } +} + +type fakeConfig struct { + name string + apiKey string + model string +} + +func (f fakeConfig) TTSProviderName() string { return f.name } +func (f fakeConfig) GoogleAPIKey() string { return f.apiKey } +func (f fakeConfig) TTSModel() string { return f.model } -- cgit v1.2.3