154 lines
3.5 KiB
Go
154 lines
3.5 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) {
|
|
RegisterSpecGroupsIter(slices.Values(groups))
|
|
}
|
|
|
|
// RegisterSpecGroupsIter adds route groups from an iterator to the package-level
|
|
// spec registry.
|
|
//
|
|
// Nil groups are ignored. Registered groups are returned by RegisteredSpecGroups
|
|
// in the order they were added.
|
|
//
|
|
// Example:
|
|
//
|
|
// api.RegisterSpecGroupsIter(api.RegisteredSpecGroupsIter())
|
|
func RegisterSpecGroupsIter(groups iter.Seq[RouteGroup]) {
|
|
if groups == nil {
|
|
return
|
|
}
|
|
|
|
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.
|
|
//
|
|
// Example:
|
|
//
|
|
// for g := range api.RegisteredSpecGroupsIter() {
|
|
// _ = g
|
|
// }
|
|
func RegisteredSpecGroupsIter() iter.Seq[RouteGroup] {
|
|
specRegistry.mu.RLock()
|
|
groups := slices.Clone(specRegistry.groups)
|
|
specRegistry.mu.RUnlock()
|
|
|
|
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.
|
|
//
|
|
// 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()
|
|
}
|