package sources import ( "fmt" "strings" "gitea.maximumdirect.net/ejr/feedkit/config" ) // Factory constructs a configured Source instance from config. // // This is how concrete daemons (weatherfeeder/newsfeeder/...) register their // domain-specific source drivers (Open-Meteo, NWS, RSS, etc.) while feedkit // remains domain-agnostic. type Factory func(cfg config.SourceConfig) (Source, error) type StreamFactory func(cfg config.SourceConfig) (StreamSource, error) type Registry struct { byDriver map[string]Factory byStreamDriver map[string]StreamFactory } func NewRegistry() *Registry { return &Registry{ byDriver: map[string]Factory{}, byStreamDriver: map[string]StreamFactory{}, } } // Register associates a driver name (e.g. "openmeteo_observation") with a factory. // // The driver string is the "lookup key" used by config.sources[].driver. func (r *Registry) Register(driver string, f Factory) { driver = strings.TrimSpace(driver) if driver == "" { // Panic is appropriate here: registering an empty driver is always a programmer error, // and it will lead to extremely confusing runtime behavior if allowed. panic("sources.Registry.Register: driver cannot be empty") } if f == nil { panic(fmt.Sprintf("sources.Registry.Register: factory cannot be nil (driver=%q)", driver)) } if _, exists := r.byStreamDriver[driver]; exists { panic(fmt.Sprintf("sources.Registry.Register: driver %q already registered as a stream source", driver)) } r.byDriver[driver] = f } // RegisterStream is the StreamSource equivalent of Register. func (r *Registry) RegisterStream(driver string, f StreamFactory) { driver = strings.TrimSpace(driver) if driver == "" { panic("sources.Registry.RegisterStream: driver cannot be empty") } if f == nil { panic(fmt.Sprintf("sources.Registry.RegisterStream: factory cannot be nil (driver=%q)", driver)) } if _, exists := r.byDriver[driver]; exists { panic(fmt.Sprintf("sources.Registry.RegisterStream: driver %q already registered as a polling source", driver)) } r.byStreamDriver[driver] = f } // Build constructs a Source from a SourceConfig by looking up cfg.Driver. func (r *Registry) Build(cfg config.SourceConfig) (Source, error) { f, ok := r.byDriver[cfg.Driver] if !ok { return nil, fmt.Errorf("unknown source driver: %q", cfg.Driver) } return f(cfg) } // BuildInput can return either a polling Source or a StreamSource. func (r *Registry) BuildInput(cfg config.SourceConfig) (Input, error) { if f, ok := r.byStreamDriver[cfg.Driver]; ok { return f(cfg) } if f, ok := r.byDriver[cfg.Driver]; ok { return f(cfg) } return nil, fmt.Errorf("unknown source driver: %q", cfg.Driver) }