Skip to content

Commit 3954a76

Browse files
authored
feat(middleware): add sunset/deprecation header middleware (#844)
1 parent f4ab9b1 commit 3954a76

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

middleware/sunset.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package middleware
2+
3+
import (
4+
"net/http"
5+
"time"
6+
)
7+
8+
// Sunset set Deprecation/Sunset header to response
9+
// This can be used to enable Sunset in a route or a route group
10+
// For more: https://www.rfc-editor.org/rfc/rfc8594.html
11+
func Sunset(sunsetAt time.Time, links ...string) func(http.Handler) http.Handler {
12+
return func(next http.Handler) http.Handler {
13+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14+
if !sunsetAt.IsZero() {
15+
w.Header().Set("Sunset", sunsetAt.Format(http.TimeFormat))
16+
w.Header().Set("Deprecation", sunsetAt.Format(http.TimeFormat))
17+
18+
for _, link := range links {
19+
w.Header().Add("Link", link)
20+
}
21+
}
22+
next.ServeHTTP(w, r)
23+
})
24+
}
25+
}

middleware/sunset_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package middleware
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
"time"
8+
9+
"github.com/go-chi/chi/v5"
10+
)
11+
12+
func TestSunset(t *testing.T) {
13+
14+
t.Run("Sunset without link", func(t *testing.T) {
15+
req, _ := http.NewRequest("GET", "/", nil)
16+
w := httptest.NewRecorder()
17+
18+
r := chi.NewRouter()
19+
20+
sunsetAt := time.Date(2025, 12, 24, 10, 20, 0, 0, time.UTC)
21+
r.Use(Sunset(sunsetAt))
22+
23+
var sunset, deprecation string
24+
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
25+
clonedHeader := w.Header().Clone()
26+
sunset = clonedHeader.Get("Sunset")
27+
deprecation = clonedHeader.Get("Deprecation")
28+
w.Write([]byte("I'll be unavailable soon"))
29+
})
30+
r.ServeHTTP(w, req)
31+
32+
if w.Code != 200 {
33+
t.Fatal("Response Code should be 200")
34+
}
35+
36+
if sunset != "Wed, 24 Dec 2025 10:20:00 GMT" {
37+
t.Fatal("Test get sunset error.", sunset)
38+
}
39+
40+
if deprecation != "Wed, 24 Dec 2025 10:20:00 GMT" {
41+
t.Fatal("Test get deprecation error.")
42+
}
43+
})
44+
45+
t.Run("Sunset with link", func(t *testing.T) {
46+
req, _ := http.NewRequest("GET", "/", nil)
47+
w := httptest.NewRecorder()
48+
49+
r := chi.NewRouter()
50+
51+
sunsetAt := time.Date(2025, 12, 24, 10, 20, 0, 0, time.UTC)
52+
deprecationLink := "https://example.com/v1/deprecation-details"
53+
r.Use(Sunset(sunsetAt, deprecationLink))
54+
55+
var sunset, deprecation, link string
56+
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
57+
clonedHeader := w.Header().Clone()
58+
sunset = clonedHeader.Get("Sunset")
59+
deprecation = clonedHeader.Get("Deprecation")
60+
link = clonedHeader.Get("Link")
61+
62+
w.Write([]byte("I'll be unavailable soon"))
63+
})
64+
65+
r.ServeHTTP(w, req)
66+
67+
if w.Code != 200 {
68+
t.Fatal("Response Code should be 200")
69+
}
70+
71+
if sunset != "Wed, 24 Dec 2025 10:20:00 GMT" {
72+
t.Fatal("Test get sunset error.", sunset)
73+
}
74+
75+
if deprecation != "Wed, 24 Dec 2025 10:20:00 GMT" {
76+
t.Fatal("Test get deprecation error.")
77+
}
78+
79+
if link != deprecationLink {
80+
t.Fatal("Test get deprecation link error.")
81+
}
82+
})
83+
84+
}
85+
86+
/**
87+
EXAMPLE USAGES
88+
func main() {
89+
r := chi.NewRouter()
90+
91+
sunsetAt := time.Date(2025, 12, 24, 10, 20, 0, 0, time.UTC)
92+
r.Use(middleware.Sunset(sunsetAt))
93+
94+
// can provide additional link for updated resource
95+
// r.Use(middleware.Sunset(sunsetAt, "https://example.com/v1/deprecation-details"))
96+
97+
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
98+
w.Write([]byte("This endpoint will be removed soon"))
99+
})
100+
101+
log.Println("Listening on port: 3000")
102+
http.ListenAndServe(":3000", r)
103+
}
104+
**/

0 commit comments

Comments
 (0)