feat(openapi): mark deprecated operations in spec

Expose route-level deprecation in generated OpenAPI operations and cover it with a regression test.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 20:40:53 +00:00
parent a589d3bac6
commit 071de51bb5
4 changed files with 48 additions and 0 deletions

View file

@ -128,6 +128,7 @@ type RouteDescription struct {
Summary string
Description string
Tags []string
Deprecated bool
StatusCode int
Parameters []ParameterDescription
RequestBody map[string]any

View file

@ -39,6 +39,8 @@ type RouteDescription struct {
Summary string // Short summary
Description string // Long description
Tags []string // OpenAPI tags for grouping
// Deprecated marks the operation as deprecated in OpenAPI.
Deprecated bool
// StatusCode is the documented 2xx success status code.
// Zero defaults to 200.
StatusCode int

View file

@ -166,6 +166,9 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any {
"operationId": operationID(method, fullPath, operationIDs),
"responses": operationResponses(method, rd.StatusCode, rd.Response),
}
if rd.Deprecated {
operation["deprecated"] = true
}
if rd.Security != nil {
operation["security"] = rd.Security
} else {

View file

@ -1129,6 +1129,48 @@ func TestSpecBuilder_Good_DefaultTagsFromGroupName(t *testing.T) {
}
}
func TestSpecBuilder_Good_DeprecatedOperation(t *testing.T) {
sb := &api.SpecBuilder{
Title: "Test",
Version: "1.0.0",
}
group := &specStubGroup{
name: "legacy",
basePath: "/api/legacy",
descs: []api.RouteDescription{
{
Method: "GET",
Path: "/status",
Summary: "Check legacy status",
Deprecated: true,
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)
}
op := spec["paths"].(map[string]any)["/api/legacy/status"].(map[string]any)["get"].(map[string]any)
deprecated, ok := op["deprecated"].(bool)
if !ok {
t.Fatalf("expected deprecated boolean, got %T", op["deprecated"])
}
if !deprecated {
t.Fatal("expected deprecated operation to be marked true")
}
}
func TestSpecBuilder_Good_BlankTagsAreIgnored(t *testing.T) {
sb := &api.SpecBuilder{
Title: "Test",