From f0d25392a814cc4a1806d3c14637c932a2cd205f Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 22:19:56 +0000 Subject: [PATCH] feat(provider): add iterator for provider info summaries Co-Authored-By: Virgil --- pkg/provider/registry.go | 34 ++++++++++++++++++++++++++++++++++ pkg/provider/registry_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/pkg/provider/registry.go b/pkg/provider/registry.go index 61391f7..3df69ba 100644 --- a/pkg/provider/registry.go +++ b/pkg/provider/registry.go @@ -153,6 +153,40 @@ func (r *Registry) Info() []ProviderInfo { 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 { diff --git a/pkg/provider/registry_test.go b/pkg/provider/registry_test.go index 5b3b3cc..dab9fe6 100644 --- a/pkg/provider/registry_test.go +++ b/pkg/provider/registry_test.go @@ -173,6 +173,40 @@ func TestRegistry_Info_Good_ProxyMetadata(t *testing.T) { assert.Equal(t, "http://127.0.0.1:9999", info.Upstream) } +func TestRegistry_InfoIter_Good(t *testing.T) { + reg := provider.NewRegistry() + reg.Add(&fullProvider{}) + + var infos []provider.ProviderInfo + for info := range reg.InfoIter() { + infos = append(infos, info) + } + + require.Len(t, infos, 1) + info := infos[0] + assert.Equal(t, "full", info.Name) + assert.Equal(t, "/api/full", info.BasePath) + assert.Equal(t, []string{"stub.event"}, info.Channels) + require.NotNil(t, info.Element) + assert.Equal(t, "core-full-panel", info.Element.Tag) +} + +func TestRegistry_InfoIter_Good_SnapshotCurrentProviders(t *testing.T) { + reg := provider.NewRegistry() + reg.Add(&fullProvider{}) + + iter := reg.InfoIter() + reg.Add(&specFileProvider{specFile: "/tmp/later.json"}) + + var infos []provider.ProviderInfo + for info := range iter { + infos = append(infos, info) + } + + require.Len(t, infos, 1) + assert.Equal(t, "full", infos[0].Name) +} + func TestRegistry_Iter_Good(t *testing.T) { reg := provider.NewRegistry() reg.Add(&stubProvider{})