diff --git a/openapi.go b/openapi.go index 3278b82..44da110 100644 --- a/openapi.go +++ b/openapi.go @@ -182,6 +182,7 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) { }, "securitySchemes": securitySchemeComponents(sb.SecuritySchemes), "headers": deprecationHeaderComponents(), + "responses": responseComponents(), } return json.MarshalIndent(spec, "", " ") @@ -631,6 +632,78 @@ func deprecationHeaderComponents() map[string]any { } } +// responseComponents returns reusable OpenAPI response objects for the +// common error cases exposed by the framework. The path operations still +// inline their concrete headers so existing callers keep the same output, +// but these components make the response catalogue available for reuse. +func responseComponents() map[string]any { + return map[string]any{ + "BadRequest": map[string]any{ + "description": "Bad request", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": envelopeSchema(nil), + }, + }, + "headers": standardResponseHeaders(), + }, + "Unauthorized": map[string]any{ + "description": "Unauthorised", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": envelopeSchema(nil), + }, + }, + "headers": standardResponseHeaders(), + }, + "Forbidden": map[string]any{ + "description": "Forbidden", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": envelopeSchema(nil), + }, + }, + "headers": standardResponseHeaders(), + }, + "RateLimitExceeded": map[string]any{ + "description": "Too many requests", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": envelopeSchema(nil), + }, + }, + "headers": mergeHeaders(standardResponseHeaders(), rateLimitHeaders()), + }, + "GatewayTimeout": map[string]any{ + "description": "Gateway timeout", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": envelopeSchema(nil), + }, + }, + "headers": standardResponseHeaders(), + }, + "InternalServerError": map[string]any{ + "description": "Internal server error", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": envelopeSchema(nil), + }, + }, + "headers": standardResponseHeaders(), + }, + "Gone": map[string]any{ + "description": "Gone", + "content": map[string]any{ + "application/json": map[string]any{ + "schema": envelopeSchema(nil), + }, + }, + "headers": mergeHeaders(standardResponseHeaders(), deprecationResponseHeaders(true, "", "")), + }, + } +} + // securitySchemeComponents builds the OpenAPI security scheme registry. // bearerAuth stays available by default, while callers can add or override // additional scheme definitions for custom security requirements. diff --git a/openapi_test.go b/openapi_test.go index f726ca0..18fd5b7 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -257,6 +257,39 @@ func TestSpecBuilder_Good_CustomSecuritySchemesAreMerged(t *testing.T) { } } +func TestSpecBuilder_Good_CommonResponseComponentsArePublished(t *testing.T) { + sb := &api.SpecBuilder{ + Title: "Test", + Version: "1.0.0", + } + + data, err := sb.Build(nil) + 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) + } + + components := spec["components"].(map[string]any) + responses := components["responses"].(map[string]any) + for _, name := range []string{ + "BadRequest", + "Unauthorized", + "Forbidden", + "RateLimitExceeded", + "GatewayTimeout", + "InternalServerError", + "Gone", + } { + if _, ok := responses[name]; !ok { + t.Fatalf("expected %s response component in spec", name) + } + } +} + func TestSpecBuilder_Good_SwaggerUIPathExtension(t *testing.T) { sb := &api.SpecBuilder{ Title: "Test",