api/spec_registry.go
Virgil cb726000a9 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>
2026-04-01 21:03:15 +00:00

99 lines
2.3 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package api
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
// API CLI will pick them up when building specs or SDKs.
var specRegistry struct {
mu sync.RWMutex
groups []RouteGroup
}
// RegisterSpecGroups adds route groups to the package-level spec registry.
// Nil groups are ignored. Registered groups are returned by RegisteredSpecGroups
// in the order they were added.
//
// Example:
//
// api.RegisterSpecGroups(api.NewToolBridge("/mcp"))
func RegisterSpecGroups(groups ...RouteGroup) {
specRegistry.mu.Lock()
defer specRegistry.mu.Unlock()
for _, group := range groups {
if group == nil {
continue
}
if specRegistryContains(group) {
continue
}
specRegistry.groups = append(specRegistry.groups, group)
}
}
// RegisteredSpecGroups returns a copy of the route groups registered for
// CLI-generated OpenAPI documents.
//
// Example:
//
// groups := api.RegisteredSpecGroups()
func RegisteredSpecGroups() []RouteGroup {
specRegistry.mu.RLock()
defer specRegistry.mu.RUnlock()
out := make([]RouteGroup, len(specRegistry.groups))
copy(out, specRegistry.groups)
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.
//
// Example:
//
// api.ResetSpecGroups()
func ResetSpecGroups() {
specRegistry.mu.Lock()
defer specRegistry.mu.Unlock()
specRegistry.groups = nil
}
func specRegistryContains(group RouteGroup) bool {
key := specGroupKey(group)
for _, existing := range specRegistry.groups {
if specGroupKey(existing) == key {
return true
}
}
return false
}
func specGroupKey(group RouteGroup) string {
if group == nil {
return ""
}
return group.Name() + "\x00" + group.BasePath()
}