refactor(api): centralise spec group iterator
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
ed5822058d
commit
8149b0abf2
3 changed files with 59 additions and 26 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue