feat(api): collapse equivalent OpenAPI servers

Normalise server metadata so trailing-slash variants deduplicate to a single entry.

Adds a regression test covering both absolute and relative server URLs.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 20:01:34 +00:00
parent 6034579c00
commit 6017ac7132
2 changed files with 60 additions and 1 deletions

View file

@ -1252,3 +1252,43 @@ func TestSpecBuilder_Good_Servers(t *testing.T) {
t.Fatalf("expected second server url=%q, got %v", "/", second["url"])
}
}
func TestSpecBuilder_Good_ServersCollapseTrailingSlashes(t *testing.T) {
sb := &api.SpecBuilder{
Title: "Test",
Version: "1.0.0",
Servers: []string{
"https://api.example.com/",
"https://api.example.com",
"/api/",
"/api",
},
}
data, err := sb.Build(nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var spec map[string]any
if err := json.Unmarshal(data, &spec); err != nil {
t.Fatalf("invalid JSON: %v", err)
}
servers, ok := spec["servers"].([]any)
if !ok {
t.Fatalf("expected servers array, got %T", spec["servers"])
}
if len(servers) != 2 {
t.Fatalf("expected 2 collapsed servers, got %d", len(servers))
}
first := servers[0].(map[string]any)
if first["url"] != "https://api.example.com" {
t.Fatalf("expected first server url=%q, got %v", "https://api.example.com", first["url"])
}
second := servers[1].(map[string]any)
if second["url"] != "/api" {
t.Fatalf("expected second server url=%q, got %v", "/api", second["url"])
}
}

View file

@ -15,7 +15,7 @@ func normaliseServers(servers []string) []string {
seen := make(map[string]struct{}, len(servers))
for _, server := range servers {
server = strings.TrimSpace(server)
server = normaliseServer(server)
if server == "" {
continue
}
@ -32,3 +32,22 @@ func normaliseServers(servers []string) []string {
return cleaned
}
// normaliseServer trims surrounding whitespace and removes a trailing slash
// from non-root server URLs so equivalent metadata collapses to one entry.
func normaliseServer(server string) string {
server = strings.TrimSpace(server)
if server == "" {
return ""
}
if server == "/" {
return server
}
server = strings.TrimRight(server, "/")
if server == "" {
return "/"
}
return server
}