feat(api): dedupe registered spec groups

Prevent duplicate RouteGroup registrations from appearing multiple times in CLI-generated OpenAPI output. This keeps spec generation stable when packages register the same group more than once during init or tests.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 19:06:03 +00:00
parent 69beb451b5
commit 0144244ccd
2 changed files with 77 additions and 0 deletions

View file

@ -23,6 +23,9 @@ func RegisterSpecGroups(groups ...RouteGroup) {
if group == nil {
continue
}
if specRegistryContains(group) {
continue
}
specRegistry.groups = append(specRegistry.groups, group)
}
}
@ -37,3 +40,30 @@ func RegisteredSpecGroups() []RouteGroup {
copy(out, specRegistry.groups)
return out
}
// ResetSpecGroups clears the package-level spec registry.
// It is primarily intended for tests that need to isolate global state.
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()
}

47
spec_registry_test.go Normal file
View file

@ -0,0 +1,47 @@
// SPDX-License-Identifier: EUPL-1.2
package api_test
import (
"testing"
"github.com/gin-gonic/gin"
api "dappco.re/go/core/api"
)
type specRegistryStubGroup struct {
name string
basePath string
}
func (g *specRegistryStubGroup) Name() string { return g.name }
func (g *specRegistryStubGroup) BasePath() string { return g.basePath }
func (g *specRegistryStubGroup) RegisterRoutes(rg *gin.RouterGroup) {}
func TestRegisterSpecGroups_Good_DeduplicatesByIdentity(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: "alpha", basePath: "/alpha"}
third := &specRegistryStubGroup{name: "beta", basePath: "/beta"}
api.RegisterSpecGroups(nil, first, second, third, first)
groups := api.RegisteredSpecGroups()
if len(groups) != 2 {
t.Fatalf("expected 2 unique groups, got %d", len(groups))
}
if groups[0].Name() != "alpha" || groups[0].BasePath() != "/alpha" {
t.Fatalf("expected first group to be alpha at /alpha, got %s at %s", groups[0].Name(), groups[0].BasePath())
}
if groups[1].Name() != "beta" || groups[1].BasePath() != "/beta" {
t.Fatalf("expected second group to be beta at /beta, got %s at %s", groups[1].Name(), groups[1].BasePath())
}
}