feat(api): emit request bodies for non-GET operations

Keep OpenAPI requestBody generation aligned with the RouteDescription contract by allowing non-GET operations, including DELETE, to declare JSON bodies.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 14:04:04 +00:00
parent c4cbd018ac
commit fb6812df09
2 changed files with 47 additions and 1 deletions

View file

@ -136,7 +136,8 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any {
}
// Add request body for methods that accept one.
if rd.RequestBody != nil && (method == "post" || method == "put" || method == "patch") {
// The contract only excludes GET; other verbs may legitimately carry bodies.
if rd.RequestBody != nil && method != "get" && method != "head" {
operation["requestBody"] = map[string]any{
"required": true,
"content": map[string]any{

View file

@ -328,6 +328,51 @@ func TestSpecBuilder_Good_OperationIDPreservesPathParams(t *testing.T) {
}
}
func TestSpecBuilder_Good_RequestBodyOnDelete(t *testing.T) {
sb := &api.SpecBuilder{
Title: "Test",
Version: "1.0.0",
}
group := &specStubGroup{
name: "resources",
basePath: "/api",
descs: []api.RouteDescription{
{
Method: "DELETE",
Path: "/resources/{id}",
Summary: "Delete resource",
Tags: []string{"resources"},
RequestBody: map[string]any{
"type": "object",
"properties": map[string]any{
"reason": map[string]any{"type": "string"},
},
},
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)
}
paths := spec["paths"].(map[string]any)
deleteOp := paths["/api/resources/{id}"].(map[string]any)["delete"].(map[string]any)
if deleteOp["requestBody"] == nil {
t.Fatal("expected requestBody on DELETE /api/resources/{id}")
}
}
func TestSpecBuilder_Good_NonDescribableGroup(t *testing.T) {
sb := &api.SpecBuilder{
Title: "Test",