feat(api): add iterator for spec registry

Adds RegisteredSpecGroupsIter so CLI consumers can range over a snapshot of the package-level spec registry without copying the slice manually.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 21:03:15 +00:00
parent f2f262a4c2
commit cb726000a9
2 changed files with 49 additions and 1 deletions

View file

@ -2,7 +2,12 @@
package api
import "sync"
import (
"iter"
"sync"
"slices"
)
// specRegistry stores RouteGroups that should be included in CLI-generated
// OpenAPI documents. Packages can register their groups during init and the
@ -49,6 +54,19 @@ func RegisteredSpecGroups() []RouteGroup {
return out
}
// RegisteredSpecGroupsIter returns an iterator over the route groups registered
// for CLI-generated OpenAPI documents.
//
// The iterator snapshots the current registry contents so callers can range
// over it without holding the registry lock.
func RegisteredSpecGroupsIter() iter.Seq[RouteGroup] {
specRegistry.mu.RLock()
groups := slices.Clone(specRegistry.groups)
specRegistry.mu.RUnlock()
return slices.Values(groups)
}
// ResetSpecGroups clears the package-level spec registry.
// It is primarily intended for tests that need to isolate global state.
//

View file

@ -45,3 +45,33 @@ func TestRegisterSpecGroups_Good_DeduplicatesByIdentity(t *testing.T) {
t.Fatalf("expected second group to be beta at /beta, got %s at %s", groups[1].Name(), groups[1].BasePath())
}
}
func TestRegisterSpecGroups_Good_IteratorReturnsSnapshot(t *testing.T) {
snapshot := api.RegisteredSpecGroups()
api.ResetSpecGroups()
t.Cleanup(func() {
api.ResetSpecGroups()
api.RegisterSpecGroups(snapshot...)
})
first := &specRegistryStubGroup{name: "alpha", basePath: "/alpha"}
second := &specRegistryStubGroup{name: "beta", basePath: "/beta"}
api.RegisterSpecGroups(first)
iter := api.RegisteredSpecGroupsIter()
api.RegisterSpecGroups(second)
var groups []api.RouteGroup
for group := range iter {
groups = append(groups, group)
}
if len(groups) != 1 {
t.Fatalf("expected iterator snapshot to contain 1 group, got %d", len(groups))
}
if groups[0].Name() != "alpha" || groups[0].BasePath() != "/alpha" {
t.Fatalf("expected iterator snapshot to preserve alpha at /alpha, got %s at %s", groups[0].Name(), groups[0].BasePath())
}
}