diff --git a/openapi.go b/openapi.go index aa9f8f5..a7bebbf 100644 --- a/openapi.go +++ b/openapi.go @@ -212,9 +212,15 @@ func (sb *SpecBuilder) buildPaths(groups []preparedRouteGroup) map[string]any { // Add request body for methods that accept one. // The contract only excludes GET; other verbs may legitimately carry bodies. - if rd.RequestBody != nil && method != "get" { + // An example-only request body still produces a documented payload so + // callers can see the expected shape even when a schema is omitted. + if method != "get" && (rd.RequestBody != nil || rd.RequestExample != nil) { + requestSchema := rd.RequestBody + if requestSchema == nil { + requestSchema = map[string]any{} + } requestMediaType := map[string]any{ - "schema": rd.RequestBody, + "schema": requestSchema, } if rd.RequestExample != nil { requestMediaType["example"] = rd.RequestExample diff --git a/openapi_test.go b/openapi_test.go index 6e71362..10febd2 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -1003,6 +1003,55 @@ func TestSpecBuilder_Good_RequestBodyOnHead(t *testing.T) { } } +func TestSpecBuilder_Good_RequestExampleWithoutSchema(t *testing.T) { + sb := &api.SpecBuilder{ + Title: "Test", + Version: "1.0.0", + } + + group := &specStubGroup{ + name: "resources", + basePath: "/api", + descs: []api.RouteDescription{ + { + Method: "POST", + Path: "/resources", + Summary: "Create resource", + Tags: []string{"resources"}, + RequestExample: map[string]any{ + "name": "Example resource", + }, + 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) + } + + postOp := spec["paths"].(map[string]any)["/api/resources"].(map[string]any)["post"].(map[string]any) + requestBody := postOp["requestBody"].(map[string]any) + appJSON := requestBody["content"].(map[string]any)["application/json"].(map[string]any) + + if appJSON["example"].(map[string]any)["name"] != "Example resource" { + t.Fatalf("expected request example to be preserved, got %v", appJSON["example"]) + } + + schema := appJSON["schema"].(map[string]any) + if len(schema) != 0 { + t.Fatalf("expected example-only request body to use an empty schema, got %v", schema) + } +} + func TestSpecBuilder_Good_PathParameters(t *testing.T) { sb := &api.SpecBuilder{ Title: "Test",