refactor(api): centralise spec group iterator

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 07:57:58 +00:00
parent ed5822058d
commit 8149b0abf2
3 changed files with 59 additions and 26 deletions

View file

@ -12,30 +12,5 @@ import (
// extra group. It keeps the command paths iterator-backed while preserving the
// existing ordering guarantees.
func specGroupsIter(extra goapi.RouteGroup) iter.Seq[goapi.RouteGroup] {
return func(yield func(goapi.RouteGroup) bool) {
seen := map[string]struct{}{}
for group := range goapi.RegisteredSpecGroupsIter() {
key := specGroupIterKey(group)
seen[key] = struct{}{}
if !yield(group) {
return
}
}
if extra != nil {
if _, ok := seen[specGroupIterKey(extra)]; ok {
return
}
if !yield(extra) {
return
}
}
}
}
func specGroupIterKey(group goapi.RouteGroup) string {
if group == nil {
return ""
}
return group.Name() + "\x00" + group.BasePath()
return goapi.SpecGroupsIter(extra)
}

View file

@ -90,6 +90,38 @@ func RegisteredSpecGroupsIter() iter.Seq[RouteGroup] {
return slices.Values(groups)
}
// SpecGroupsIter returns the registered spec groups plus one optional extra
// group, deduplicated by group identity.
//
// The iterator snapshots the registry before yielding so callers can range
// over it without holding the registry lock.
//
// Example:
//
// for g := range api.SpecGroupsIter(api.NewToolBridge("/tools")) {
// _ = g
// }
func SpecGroupsIter(extra RouteGroup) iter.Seq[RouteGroup] {
return func(yield func(RouteGroup) bool) {
seen := map[string]struct{}{}
for group := range RegisteredSpecGroupsIter() {
key := specGroupKey(group)
seen[key] = struct{}{}
if !yield(group) {
return
}
}
if extra != nil {
if _, ok := seen[specGroupKey(extra)]; ok {
return
}
if !yield(extra) {
return
}
}
}
}
// ResetSpecGroups clears the package-level spec registry.
// It is primarily intended for tests that need to isolate global state.
//

View file

@ -110,3 +110,29 @@ func TestRegisterSpecGroupsIter_Good_DeduplicatesAndRegisters(t *testing.T) {
t.Fatalf("expected second group to be gamma at /gamma, got %s at %s", registered[1].Name(), registered[1].BasePath())
}
}
func TestSpecGroupsIter_Good_DeduplicatesExtraBridge(t *testing.T) {
snapshot := api.RegisteredSpecGroups()
api.ResetSpecGroups()
t.Cleanup(func() {
api.ResetSpecGroups()
api.RegisterSpecGroups(snapshot...)
})
first := &specRegistryStubGroup{name: "alpha", basePath: "/alpha"}
extra := &specRegistryStubGroup{name: "alpha", basePath: "/alpha"}
api.RegisterSpecGroups(first)
var groups []api.RouteGroup
for group := range api.SpecGroupsIter(extra) {
groups = append(groups, group)
}
if len(groups) != 1 {
t.Fatalf("expected deduplicated iterator to return 1 group, got %d", len(groups))
}
if groups[0].Name() != "alpha" || groups[0].BasePath() != "/alpha" {
t.Fatalf("expected alpha at /alpha, got %s at %s", groups[0].Name(), groups[0].BasePath())
}
}