2026-04-01 23:02:52 +00:00
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
|
|
|
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/http"
|
|
|
|
|
"time"
|
|
|
|
|
|
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.
Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
|
|
|
core "dappco.re/go/core"
|
|
|
|
|
|
2026-04-01 23:02:52 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-14 14:51:04 +01:00
|
|
|
// SunsetOption customises the behaviour of ApiSunsetWith. Use the supplied
|
|
|
|
|
// constructors (e.g. WithSunsetNoticeURL) to compose the desired metadata
|
|
|
|
|
// without breaking the simpler ApiSunset signature.
|
|
|
|
|
//
|
|
|
|
|
// mw := api.ApiSunsetWith("2025-06-01", "/api/v2/users",
|
|
|
|
|
// api.WithSunsetNoticeURL("https://docs.example.com/deprecation/billing"),
|
|
|
|
|
// )
|
|
|
|
|
type SunsetOption func(*sunsetConfig)
|
|
|
|
|
|
|
|
|
|
// sunsetConfig carries optional metadata for ApiSunsetWith.
|
|
|
|
|
type sunsetConfig struct {
|
|
|
|
|
noticeURL string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithSunsetNoticeURL adds the API-Deprecation-Notice-URL header documented
|
|
|
|
|
// in spec §8 to every response. The URL should point to a human-readable
|
|
|
|
|
// migration guide for the deprecated endpoint.
|
|
|
|
|
//
|
|
|
|
|
// api.ApiSunsetWith("2026-04-30", "POST /api/v2/billing/invoices",
|
|
|
|
|
// api.WithSunsetNoticeURL("https://docs.api.dappco.re/deprecation/billing"),
|
|
|
|
|
// )
|
|
|
|
|
func WithSunsetNoticeURL(url string) SunsetOption {
|
|
|
|
|
return func(cfg *sunsetConfig) {
|
|
|
|
|
cfg.noticeURL = url
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 23:02:52 +00:00
|
|
|
// ApiSunset returns middleware that marks a route or group as deprecated.
|
|
|
|
|
//
|
2026-04-01 23:33:52 +00:00
|
|
|
// The middleware appends standard deprecation headers to every response:
|
2026-04-14 14:51:04 +01:00
|
|
|
// Deprecation, optional Sunset, optional Link, optional API-Suggested-Replacement,
|
|
|
|
|
// and X-API-Warn. Existing header values are preserved so downstream middleware
|
|
|
|
|
// and handlers can keep their own link relations or warning metadata.
|
2026-04-01 23:02:52 +00:00
|
|
|
//
|
|
|
|
|
// Example:
|
|
|
|
|
//
|
|
|
|
|
// rg.Use(api.ApiSunset("2025-06-01", "/api/v2/users"))
|
|
|
|
|
func ApiSunset(sunsetDate, replacement string) gin.HandlerFunc {
|
2026-04-14 14:51:04 +01:00
|
|
|
return ApiSunsetWith(sunsetDate, replacement)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ApiSunsetWith is the extensible form of ApiSunset. It accepts SunsetOption
|
|
|
|
|
// values to attach optional metadata such as the deprecation notice URL.
|
|
|
|
|
//
|
|
|
|
|
// Example:
|
|
|
|
|
//
|
|
|
|
|
// rg.Use(api.ApiSunsetWith(
|
|
|
|
|
// "2026-04-30",
|
|
|
|
|
// "POST /api/v2/billing/invoices",
|
|
|
|
|
// api.WithSunsetNoticeURL("https://docs.api.dappco.re/deprecation/billing"),
|
|
|
|
|
// ))
|
|
|
|
|
func ApiSunsetWith(sunsetDate, replacement string, opts ...SunsetOption) gin.HandlerFunc {
|
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.
Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
|
|
|
sunsetDate = core.Trim(sunsetDate)
|
|
|
|
|
replacement = core.Trim(replacement)
|
2026-04-01 23:02:52 +00:00
|
|
|
formatted := formatSunsetDate(sunsetDate)
|
|
|
|
|
warning := "This endpoint is deprecated."
|
|
|
|
|
if sunsetDate != "" {
|
|
|
|
|
warning = "This endpoint is deprecated and will be removed on " + sunsetDate + "."
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 14:51:04 +01:00
|
|
|
cfg := &sunsetConfig{}
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
if opt != nil {
|
|
|
|
|
opt(cfg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
noticeURL := core.Trim(cfg.noticeURL)
|
|
|
|
|
|
2026-04-01 23:02:52 +00:00
|
|
|
return func(c *gin.Context) {
|
2026-04-01 23:29:12 +00:00
|
|
|
c.Next()
|
|
|
|
|
|
2026-04-01 23:33:52 +00:00
|
|
|
c.Writer.Header().Add("Deprecation", "true")
|
2026-04-01 23:02:52 +00:00
|
|
|
if formatted != "" {
|
2026-04-01 23:33:52 +00:00
|
|
|
c.Writer.Header().Add("Sunset", formatted)
|
2026-04-01 23:02:52 +00:00
|
|
|
}
|
|
|
|
|
if replacement != "" {
|
2026-04-01 23:29:12 +00:00
|
|
|
c.Writer.Header().Add("Link", "<"+replacement+">; rel=\"successor-version\"")
|
2026-04-14 14:51:04 +01:00
|
|
|
c.Writer.Header().Add("API-Suggested-Replacement", replacement)
|
|
|
|
|
}
|
|
|
|
|
if noticeURL != "" {
|
|
|
|
|
c.Writer.Header().Add("API-Deprecation-Notice-URL", noticeURL)
|
2026-04-01 23:02:52 +00:00
|
|
|
}
|
2026-04-01 23:33:52 +00:00
|
|
|
c.Writer.Header().Add("X-API-Warn", warning)
|
2026-04-01 23:02:52 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatSunsetDate(sunsetDate string) string {
|
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.
Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
|
|
|
sunsetDate = core.Trim(sunsetDate)
|
2026-04-01 23:02:52 +00:00
|
|
|
if sunsetDate == "" {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.
Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:00 +01:00
|
|
|
if core.Contains(sunsetDate, ",") {
|
2026-04-01 23:02:52 +00:00
|
|
|
return sunsetDate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parsed, err := time.Parse("2006-01-02", sunsetDate)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return sunsetDate
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parsed.UTC().Format(http.TimeFormat)
|
|
|
|
|
}
|