From b58de8f8f0f5ff28fb593d0d881095902b77ad26 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 14:38:45 +0000 Subject: [PATCH] feat(provider): expose registry spec files Add stable registry helpers for enumerating provider OpenAPI spec files, plus iterator coverage. This gives discovery consumers a direct way to aggregate provider docs without changing routing behaviour. Co-Authored-By: Virgil --- pkg/provider/registry.go | 29 +++++++++++++++++++++++++++++ pkg/provider/registry_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/pkg/provider/registry.go b/pkg/provider/registry.go index 089f658..960a493 100644 --- a/pkg/provider/registry.go +++ b/pkg/provider/registry.go @@ -152,3 +152,32 @@ func (r *Registry) Info() []ProviderInfo { } return infos } + +// 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()) +} diff --git a/pkg/provider/registry_test.go b/pkg/provider/registry_test.go index 8059570..5b3b3cc 100644 --- a/pkg/provider/registry_test.go +++ b/pkg/provider/registry_test.go @@ -38,6 +38,13 @@ func (r *renderableProvider) Element() provider.ElementSpec { return provider.ElementSpec{Tag: "core-stub-panel", Source: "/assets/stub.js"} } +type specFileProvider struct { + stubProvider + specFile string +} + +func (s *specFileProvider) SpecFile() string { return s.specFile } + type fullProvider struct { streamableProvider } @@ -177,3 +184,27 @@ func TestRegistry_Iter_Good(t *testing.T) { } assert.Equal(t, 2, count) } + +func TestRegistry_SpecFiles_Good(t *testing.T) { + reg := provider.NewRegistry() + reg.Add(&stubProvider{}) + reg.Add(&specFileProvider{specFile: "/tmp/b.json"}) + reg.Add(&specFileProvider{specFile: "/tmp/a.yaml"}) + reg.Add(&specFileProvider{specFile: "/tmp/a.yaml"}) + reg.Add(&specFileProvider{specFile: ""}) + + assert.Equal(t, []string{"/tmp/a.yaml", "/tmp/b.json"}, reg.SpecFiles()) +} + +func TestRegistry_SpecFilesIter_Good(t *testing.T) { + reg := provider.NewRegistry() + reg.Add(&specFileProvider{specFile: "/tmp/z.json"}) + reg.Add(&specFileProvider{specFile: "/tmp/x.json"}) + + var files []string + for file := range reg.SpecFilesIter() { + files = append(files, file) + } + + assert.Equal(t, []string{"/tmp/x.json", "/tmp/z.json"}, files) +}