From 4bc132f101e37a63522f824d26e66d4c4d0377d3 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 14:46:15 +0000 Subject: [PATCH] feat(api): fall back to group tags in openapi Co-Authored-By: Virgil --- openapi.go | 16 +++++++++++++++- openapi_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/openapi.go b/openapi.go index 8ef928f..0e26ed5 100644 --- a/openapi.go +++ b/openapi.go @@ -108,7 +108,7 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any { operation := map[string]any{ "summary": rd.Summary, "description": rd.Description, - "tags": rd.Tags, + "tags": resolvedOperationTags(g, rd), "operationId": operationID(method, fullPath, operationIDs), "security": []any{ map[string]any{ @@ -191,6 +191,20 @@ func (sb *SpecBuilder) buildTags(groups []RouteGroup) []map[string]any { return tags } +// resolvedOperationTags returns the explicit route tags when provided, or a +// stable fallback derived from the group's name when the route omits tags. +func resolvedOperationTags(g RouteGroup, rd RouteDescription) []string { + if len(rd.Tags) > 0 { + return rd.Tags + } + + if name := g.Name(); name != "" { + return []string{name} + } + + return nil +} + // envelopeSchema wraps a data schema in the standard Response[T] envelope. func envelopeSchema(dataSchema map[string]any) map[string]any { properties := map[string]any{ diff --git a/openapi_test.go b/openapi_test.go index e513224..093d068 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -468,6 +468,47 @@ func TestSpecBuilder_Good_NonDescribableGroup(t *testing.T) { } } +func TestSpecBuilder_Good_DefaultTagsFromGroupName(t *testing.T) { + sb := &api.SpecBuilder{ + Title: "Test", + Version: "1.0.0", + } + + group := &specStubGroup{ + name: "fallback", + basePath: "/api/fallback", + descs: []api.RouteDescription{ + { + Method: "GET", + Path: "/status", + Summary: "Check status", + Response: map[string]any{ + "type": "object", + }, + }, + }, + } + + data, err := sb.Build([]api.RouteGroup{group}) + 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) + } + + operation := spec["paths"].(map[string]any)["/api/fallback/status"].(map[string]any)["get"].(map[string]any) + tags, ok := operation["tags"].([]any) + if !ok { + t.Fatalf("expected tags array, got %T", operation["tags"]) + } + if len(tags) != 1 || tags[0] != "fallback" { + t.Fatalf("expected fallback tag from group name, got %v", operation["tags"]) + } +} + func TestSpecBuilder_Good_ToolBridgeIntegration(t *testing.T) { gin.SetMode(gin.TestMode)