Files
Memoh/internal/channel/registry.go
T

365 lines
10 KiB
Go

package channel
import (
"context"
"errors"
"fmt"
"strings"
"sync"
)
// Registry holds all registered channel adapters and provides dispatch methods
// for configuration normalization, target resolution, and binding operations.
// It replaces the former global registry, and must be created via NewRegistry
// and passed explicitly to components that need it.
type Registry struct {
mu sync.RWMutex
adapters map[ChannelType]Adapter
}
// NewRegistry creates an empty Registry.
func NewRegistry() *Registry {
return &Registry{
adapters: map[ChannelType]Adapter{},
}
}
// Register adds an adapter to the registry.
func (r *Registry) Register(adapter Adapter) error {
if adapter == nil {
return errors.New("adapter is nil")
}
ct := normalizeChannelType(adapter.Type().String())
if ct == "" {
return errors.New("channel type is required")
}
r.mu.Lock()
defer r.mu.Unlock()
if _, exists := r.adapters[ct]; exists {
return fmt.Errorf("channel type already registered: %s", ct)
}
r.adapters[ct] = adapter
return nil
}
// MustRegister calls Register and panics on error.
func (r *Registry) MustRegister(adapter Adapter) {
if err := r.Register(adapter); err != nil {
panic(err)
}
}
// Unregister removes a channel type from the registry.
func (r *Registry) Unregister(channelType ChannelType) bool {
ct := normalizeChannelType(channelType.String())
if ct == "" {
return false
}
r.mu.Lock()
defer r.mu.Unlock()
if _, exists := r.adapters[ct]; !exists {
return false
}
delete(r.adapters, ct)
return true
}
// Get returns the adapter for the given channel type.
func (r *Registry) Get(channelType ChannelType) (Adapter, bool) {
ct := normalizeChannelType(channelType.String())
r.mu.RLock()
defer r.mu.RUnlock()
adapter, ok := r.adapters[ct]
return adapter, ok
}
// DirectoryAdapter returns the directory adapter for the given channel type if it implements ChannelDirectoryAdapter.
func (r *Registry) DirectoryAdapter(channelType ChannelType) (ChannelDirectoryAdapter, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
dir, ok := adapter.(ChannelDirectoryAdapter)
return dir, ok
}
// List returns all registered adapters.
func (r *Registry) List() []Adapter {
r.mu.RLock()
defer r.mu.RUnlock()
items := make([]Adapter, 0, len(r.adapters))
for _, a := range r.adapters {
items = append(items, a)
}
return items
}
// Types returns all registered channel types.
func (r *Registry) Types() []ChannelType {
r.mu.RLock()
defer r.mu.RUnlock()
items := make([]ChannelType, 0, len(r.adapters))
for ct := range r.adapters {
items = append(items, ct)
}
return items
}
// --- Descriptor accessors ---
// GetDescriptor returns the descriptor for the given channel type.
func (r *Registry) GetDescriptor(channelType ChannelType) (Descriptor, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return Descriptor{}, false
}
return adapter.Descriptor(), true
}
// ListDescriptors returns descriptors for all registered channel types.
func (r *Registry) ListDescriptors() []Descriptor {
adapters := r.List()
items := make([]Descriptor, 0, len(adapters))
for _, a := range adapters {
items = append(items, a.Descriptor())
}
return items
}
// ParseChannelType validates and normalizes a raw string into a registered ChannelType.
func (r *Registry) ParseChannelType(raw string) (ChannelType, error) {
ct := normalizeChannelType(raw)
if ct == "" {
return "", fmt.Errorf("unsupported channel type: %s", raw)
}
if _, ok := r.Get(ct); !ok {
return "", fmt.Errorf("unsupported channel type: %s", raw)
}
return ct, nil
}
// --- Capability accessors ---
// GetCapabilities returns the capability matrix for the given channel type.
func (r *Registry) GetCapabilities(channelType ChannelType) (ChannelCapabilities, bool) {
desc, ok := r.GetDescriptor(channelType)
if !ok {
return ChannelCapabilities{}, false
}
return desc.Capabilities, true
}
// GetOutboundPolicy returns the outbound policy for the given channel type.
func (r *Registry) GetOutboundPolicy(channelType ChannelType) (OutboundPolicy, bool) {
desc, ok := r.GetDescriptor(channelType)
if !ok {
return OutboundPolicy{}, false
}
return desc.OutboundPolicy, true
}
// GetConfigSchema returns the configuration schema for the given channel type.
func (r *Registry) GetConfigSchema(channelType ChannelType) (ConfigSchema, bool) {
desc, ok := r.GetDescriptor(channelType)
if !ok {
return ConfigSchema{}, false
}
return desc.ConfigSchema, true
}
// GetUserConfigSchema returns the user-binding configuration schema.
func (r *Registry) GetUserConfigSchema(channelType ChannelType) (ConfigSchema, bool) {
desc, ok := r.GetDescriptor(channelType)
if !ok {
return ConfigSchema{}, false
}
return desc.UserConfigSchema, true
}
// IsConfigless reports whether the channel type operates without per-bot configuration.
func (r *Registry) IsConfigless(channelType ChannelType) bool {
desc, ok := r.GetDescriptor(channelType)
if !ok {
return false
}
return desc.Configless
}
// --- Sender / Receiver accessors ---
// GetSender returns the Sender for the given channel type, or nil if unsupported.
func (r *Registry) GetSender(channelType ChannelType) (Sender, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
sender, ok := adapter.(Sender)
return sender, ok
}
// GetStreamSender returns the StreamSender for the given channel type, or nil if unsupported.
func (r *Registry) GetStreamSender(channelType ChannelType) (StreamSender, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
streamSender, ok := adapter.(StreamSender)
return streamSender, ok
}
// GetMessageEditor returns the MessageEditor for the given channel type, or nil if unsupported.
func (r *Registry) GetMessageEditor(channelType ChannelType) (MessageEditor, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
editor, ok := adapter.(MessageEditor)
return editor, ok
}
// GetReactor returns the Reactor for the given channel type, or nil if unsupported.
func (r *Registry) GetReactor(channelType ChannelType) (Reactor, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
reactor, ok := adapter.(Reactor)
return reactor, ok
}
// GetReceiver returns the Receiver for the given channel type, or nil if unsupported.
func (r *Registry) GetReceiver(channelType ChannelType) (Receiver, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
receiver, ok := adapter.(Receiver)
return receiver, ok
}
// GetProcessingStatusNotifier returns the ProcessingStatusNotifier for the given channel type, or nil if unsupported.
func (r *Registry) GetProcessingStatusNotifier(channelType ChannelType) (ProcessingStatusNotifier, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
notifier, ok := adapter.(ProcessingStatusNotifier)
return notifier, ok
}
// GetAttachmentResolver returns the AttachmentResolver for the given channel
// type, or nil if unsupported.
func (r *Registry) GetAttachmentResolver(channelType ChannelType) (AttachmentResolver, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, false
}
resolver, ok := adapter.(AttachmentResolver)
return resolver, ok
}
// DiscoverSelf calls the SelfDiscoverer for the given channel type if supported.
func (r *Registry) DiscoverSelf(ctx context.Context, channelType ChannelType, credentials map[string]any) (map[string]any, string, error) {
adapter, ok := r.Get(channelType)
if !ok {
return nil, "", fmt.Errorf("unsupported channel type: %s", channelType)
}
discoverer, ok := adapter.(SelfDiscoverer)
if !ok {
return nil, "", nil
}
return discoverer.DiscoverSelf(ctx, credentials)
}
// --- Dispatch methods (replace former global functions in config.go / target.go) ---
// NormalizeConfig validates and normalizes a channel configuration map.
func (r *Registry) NormalizeConfig(channelType ChannelType, raw map[string]any) (map[string]any, error) {
if raw == nil {
raw = map[string]any{}
}
adapter, ok := r.Get(channelType)
if !ok {
return nil, fmt.Errorf("unsupported channel type: %s", channelType)
}
if normalizer, ok := adapter.(ConfigNormalizer); ok {
return normalizer.NormalizeConfig(raw)
}
return raw, nil
}
// NormalizeUserConfig validates and normalizes a user-channel binding configuration.
func (r *Registry) NormalizeUserConfig(channelType ChannelType, raw map[string]any) (map[string]any, error) {
if raw == nil {
raw = map[string]any{}
}
adapter, ok := r.Get(channelType)
if !ok {
return nil, fmt.Errorf("unsupported channel type: %s", channelType)
}
if normalizer, ok := adapter.(ConfigNormalizer); ok {
return normalizer.NormalizeUserConfig(raw)
}
return raw, nil
}
// ResolveTargetFromUserConfig derives a delivery target from a user-channel binding.
func (r *Registry) ResolveTargetFromUserConfig(channelType ChannelType, config map[string]any) (string, error) {
adapter, ok := r.Get(channelType)
if !ok {
return "", fmt.Errorf("unsupported channel type: %s", channelType)
}
if resolver, ok := adapter.(TargetResolver); ok {
return resolver.ResolveTarget(config)
}
return "", fmt.Errorf("channel type %s does not support target resolution", channelType)
}
// NormalizeTarget applies the channel-specific target normalization.
func (r *Registry) NormalizeTarget(channelType ChannelType, raw string) (string, bool) {
adapter, ok := r.Get(channelType)
if !ok {
return strings.TrimSpace(raw), false
}
if resolver, ok := adapter.(TargetResolver); ok {
normalized := strings.TrimSpace(resolver.NormalizeTarget(raw))
if normalized == "" {
return "", false
}
return normalized, true
}
return strings.TrimSpace(raw), false
}
// MatchUserBinding reports whether the given binding config matches the criteria.
func (r *Registry) MatchUserBinding(channelType ChannelType, config map[string]any, criteria BindingCriteria) bool {
adapter, ok := r.Get(channelType)
if !ok {
return false
}
if matcher, ok := adapter.(BindingMatcher); ok {
return matcher.MatchBinding(config, criteria)
}
return false
}
// BuildUserBindingConfig constructs a user-channel binding config from an Identity.
func (r *Registry) BuildUserBindingConfig(channelType ChannelType, identity Identity) map[string]any {
adapter, ok := r.Get(channelType)
if !ok {
return map[string]any{}
}
if matcher, ok := adapter.(BindingMatcher); ok {
return matcher.BuildUserConfig(identity)
}
return map[string]any{}
}
func normalizeChannelType(raw string) ChannelType {
normalized := strings.TrimSpace(strings.ToLower(raw))
if normalized == "" {
return ""
}
return ChannelType(normalized)
}