// SPDX-License-Identifier: EUPL-1.2 package provider import ( "iter" "slices" "sync" "dappco.re/go/core/api" ) // Registry collects providers and mounts them on an api.Engine. // It is a convenience wrapper — providers could be registered directly // via engine.Register(), but the Registry enables discovery by consumers // (GUI, MCP) that need to query provider capabilities. type Registry struct { mu sync.RWMutex providers []Provider } // NewRegistry creates an empty provider registry. func NewRegistry() *Registry { return &Registry{} } // Add registers a provider. Providers are mounted in the order they are added. func (r *Registry) Add(p Provider) { r.mu.Lock() defer r.mu.Unlock() r.providers = append(r.providers, p) } // MountAll registers every provider with the given api.Engine. // Each provider is passed to engine.Register(), which mounts it as a // RouteGroup at its BasePath with all configured middleware. func (r *Registry) MountAll(engine *api.Engine) { r.mu.RLock() defer r.mu.RUnlock() for _, p := range r.providers { engine.Register(p) } } // List returns a copy of all registered providers. func (r *Registry) List() []Provider { r.mu.RLock() defer r.mu.RUnlock() return slices.Clone(r.providers) } // Iter returns an iterator over all registered providers. func (r *Registry) Iter() iter.Seq[Provider] { r.mu.RLock() defer r.mu.RUnlock() return slices.Values(slices.Clone(r.providers)) } // Len returns the number of registered providers. func (r *Registry) Len() int { r.mu.RLock() defer r.mu.RUnlock() return len(r.providers) } // Get returns a provider by name, or nil if not found. func (r *Registry) Get(name string) Provider { r.mu.RLock() defer r.mu.RUnlock() for _, p := range r.providers { if p.Name() == name { return p } } return nil } // Streamable returns all providers that implement the Streamable interface. func (r *Registry) Streamable() []Streamable { r.mu.RLock() defer r.mu.RUnlock() var result []Streamable for _, p := range r.providers { if s, ok := p.(Streamable); ok { result = append(result, s) } } return result } // StreamableIter returns an iterator over all registered providers that // implement the Streamable interface. func (r *Registry) StreamableIter() iter.Seq[Streamable] { r.mu.RLock() providers := slices.Clone(r.providers) r.mu.RUnlock() return func(yield func(Streamable) bool) { for _, p := range providers { if s, ok := p.(Streamable); ok { if !yield(s) { return } } } } } // Describable returns all providers that implement the Describable interface. func (r *Registry) Describable() []Describable { r.mu.RLock() defer r.mu.RUnlock() var result []Describable for _, p := range r.providers { if d, ok := p.(Describable); ok { result = append(result, d) } } return result } // DescribableIter returns an iterator over all registered providers that // implement the Describable interface. func (r *Registry) DescribableIter() iter.Seq[Describable] { r.mu.RLock() providers := slices.Clone(r.providers) r.mu.RUnlock() return func(yield func(Describable) bool) { for _, p := range providers { if d, ok := p.(Describable); ok { if !yield(d) { return } } } } } // Renderable returns all providers that implement the Renderable interface. func (r *Registry) Renderable() []Renderable { r.mu.RLock() defer r.mu.RUnlock() var result []Renderable for _, p := range r.providers { if rv, ok := p.(Renderable); ok { result = append(result, rv) } } return result } // RenderableIter returns an iterator over all registered providers that // implement the Renderable interface. func (r *Registry) RenderableIter() iter.Seq[Renderable] { r.mu.RLock() providers := slices.Clone(r.providers) r.mu.RUnlock() return func(yield func(Renderable) bool) { for _, p := range providers { if rv, ok := p.(Renderable); ok { if !yield(rv) { return } } } } } // ProviderInfo is a serialisable summary of a registered provider. type ProviderInfo struct { Name string `json:"name"` BasePath string `json:"basePath"` Channels []string `json:"channels,omitempty"` Element *ElementSpec `json:"element,omitempty"` SpecFile string `json:"specFile,omitempty"` Upstream string `json:"upstream,omitempty"` } // Info returns a summary of all registered providers. func (r *Registry) Info() []ProviderInfo { r.mu.RLock() defer r.mu.RUnlock() infos := make([]ProviderInfo, 0, len(r.providers)) for _, p := range r.providers { info := ProviderInfo{ Name: p.Name(), BasePath: p.BasePath(), } if s, ok := p.(Streamable); ok { info.Channels = s.Channels() } if rv, ok := p.(Renderable); ok { elem := rv.Element() info.Element = &elem } if sf, ok := p.(interface{ SpecFile() string }); ok { info.SpecFile = sf.SpecFile() } if up, ok := p.(interface{ Upstream() string }); ok { info.Upstream = up.Upstream() } infos = append(infos, info) } return infos } // InfoIter returns an iterator over all registered provider summaries. // The iterator snapshots the current registry contents so callers can range // over it without holding the registry lock. func (r *Registry) InfoIter() iter.Seq[ProviderInfo] { r.mu.RLock() providers := slices.Clone(r.providers) r.mu.RUnlock() return func(yield func(ProviderInfo) bool) { for _, p := range providers { info := ProviderInfo{ Name: p.Name(), BasePath: p.BasePath(), } if s, ok := p.(Streamable); ok { info.Channels = s.Channels() } if rv, ok := p.(Renderable); ok { elem := rv.Element() info.Element = &elem } if sf, ok := p.(interface{ SpecFile() string }); ok { info.SpecFile = sf.SpecFile() } if up, ok := p.(interface{ Upstream() string }); ok { info.Upstream = up.Upstream() } if !yield(info) { return } } } } // SpecFiles returns all non-empty provider OpenAPI spec file paths. // The result is deduplicated and sorted for stable discovery output. func (r *Registry) SpecFiles() []string { r.mu.RLock() defer r.mu.RUnlock() files := make(map[string]struct{}, len(r.providers)) for _, p := range r.providers { if sf, ok := p.(interface{ SpecFile() string }); ok { if path := sf.SpecFile(); path != "" { files[path] = struct{}{} } } } out := make([]string, 0, len(files)) for path := range files { out = append(out, path) } slices.Sort(out) return out } // SpecFilesIter returns an iterator over all non-empty provider OpenAPI spec files. func (r *Registry) SpecFilesIter() iter.Seq[string] { return slices.Values(r.SpecFiles()) }