diff --git a/openapi.go b/openapi.go index 5a3780b..595fb4f 100644 --- a/openapi.go +++ b/openapi.go @@ -202,6 +202,7 @@ func operationResponses(dataSchema map[string]any) map[string]any { "schema": envelopeSchema(nil), }, }, + "headers": rateLimitHeaders(), }, "504": map[string]any{ "description": "Gateway timeout", @@ -233,6 +234,7 @@ func healthResponses() map[string]any { "schema": envelopeSchema(nil), }, }, + "headers": rateLimitHeaders(), }, "504": map[string]any{ "description": "Gateway timeout", @@ -303,6 +305,20 @@ func envelopeSchema(dataSchema map[string]any) map[string]any { } } +// rateLimitHeaders documents the response headers emitted when rate limiting +// rejects a request. +func rateLimitHeaders() map[string]any { + return map[string]any{ + "Retry-After": map[string]any{ + "description": "Seconds until the rate limit resets", + "schema": map[string]any{ + "type": "integer", + "minimum": 1, + }, + }, + } +} + // operationID builds a stable OpenAPI operationId from the HTTP method and path. // The generated identifier is lower snake_case and preserves path parameter names. func operationID(method, path string, operationIDs map[string]int) string { diff --git a/openapi_test.go b/openapi_test.go index e41babd..0f25317 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -68,6 +68,11 @@ func TestSpecBuilder_Good_EmptyGroups(t *testing.T) { if _, ok := healthResponses["504"]; !ok { t.Fatal("expected 504 response on /health") } + rateLimit429 := healthResponses["429"].(map[string]any) + headers := rateLimit429["headers"].(map[string]any) + if _, ok := headers["Retry-After"]; !ok { + t.Fatal("expected Retry-After header on /health 429 response") + } // Verify system tag exists. tags := spec["tags"].([]any) @@ -240,6 +245,11 @@ func TestSpecBuilder_Good_SecuredResponses(t *testing.T) { if _, ok := responses["504"]; !ok { t.Fatal("expected 504 response in secured operation") } + rateLimit429 := responses["429"].(map[string]any) + headers := rateLimit429["headers"].(map[string]any) + if _, ok := headers["Retry-After"]; !ok { + t.Fatal("expected Retry-After header in secured operation 429 response") + } } func TestSpecBuilder_Good_EnvelopeWrapping(t *testing.T) {