diff --git a/openapi.go b/openapi.go index a3111cb..82f9dca 100644 --- a/openapi.go +++ b/openapi.go @@ -155,6 +155,7 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) { "bearerFormat": "JWT", }, }, + "headers": deprecationHeaderComponents(), } return json.MarshalIndent(spec, "", " ") @@ -507,40 +508,61 @@ func deprecationResponseHeaders(deprecated bool, sunsetDate, replacement string) headers := map[string]any{ "Deprecation": map[string]any{ - "description": "Indicates the endpoint is deprecated", - "schema": map[string]any{ - "type": "string", - }, + "$ref": "#/components/headers/deprecation", }, "X-API-Warn": map[string]any{ - "description": "Human-readable deprecation warning", - "schema": map[string]any{ - "type": "string", - }, + "$ref": "#/components/headers/xapiwarn", }, } if sunsetDate != "" { headers["Sunset"] = map[string]any{ - "description": "RFC 7231 date when the endpoint will be removed", - "schema": map[string]any{ - "type": "string", - }, + "$ref": "#/components/headers/sunset", } } if replacement != "" { headers["Link"] = map[string]any{ - "description": "Successor endpoint advertised with rel=\"successor-version\"", - "schema": map[string]any{ - "type": "string", - }, + "$ref": "#/components/headers/link", } } return headers } +// deprecationHeaderComponents returns reusable OpenAPI header components for +// the standard deprecation and sunset middleware headers. +func deprecationHeaderComponents() map[string]any { + return map[string]any{ + "deprecation": map[string]any{ + "description": "Indicates that the endpoint is deprecated.", + "schema": map[string]any{ + "type": "string", + "enum": []string{"true"}, + }, + }, + "sunset": map[string]any{ + "description": "The date and time after which the endpoint will no longer be supported.", + "schema": map[string]any{ + "type": "string", + "format": "date-time", + }, + }, + "link": map[string]any{ + "description": "Reference to the successor endpoint, when one is provided.", + "schema": map[string]any{ + "type": "string", + }, + }, + "xapiwarn": map[string]any{ + "description": "Human-readable deprecation warning for clients.", + "schema": map[string]any{ + "type": "string", + }, + }, + } +} + // buildTags generates the tags array from all RouteGroups. func (sb *SpecBuilder) buildTags(groups []preparedRouteGroup) []map[string]any { tags := []map[string]any{ diff --git a/openapi_test.go b/openapi_test.go index 1c941c9..0b702d6 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -1491,6 +1491,19 @@ func TestSpecBuilder_Good_DeprecatedOperation(t *testing.T) { t.Fatalf("expected deprecation header %q in operation response headers", name) } } + + components := spec["components"].(map[string]any) + headerComponents := components["headers"].(map[string]any) + for _, name := range []string{"deprecation", "sunset", "link", "xapiwarn"} { + if _, ok := headerComponents[name]; !ok { + t.Fatalf("expected reusable header component %q", name) + } + } + + deprecationHeader := headers["Deprecation"].(map[string]any) + if got := deprecationHeader["$ref"]; got != "#/components/headers/deprecation" { + t.Fatalf("expected Deprecation header to reference shared component, got %v", got) + } } func TestSpecBuilder_Good_BlankTagsAreIgnored(t *testing.T) {