From cb726000a9393d72683b53df2eb6898724e88173 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 21:03:15 +0000 Subject: [PATCH] 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 --- spec_registry.go | 20 +++++++++++++++++++- spec_registry_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/spec_registry.go b/spec_registry.go index 5ca94ac..264ec2c 100644 --- a/spec_registry.go +++ b/spec_registry.go @@ -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. // diff --git a/spec_registry_test.go b/spec_registry_test.go index dbd20f0..72e0789 100644 --- a/spec_registry_test.go +++ b/spec_registry_test.go @@ -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()) + } +}