errors.go
1package httperr
2
3import (
4 "net/http"
5 "net/url"
6
7 "git.kilimanjaro.io/rtw/pkg/log"
8 "git.kilimanjaro.io/rtw/pkg/middleware"
9)
10
11type Handler struct {
12 onError string
13 onUnauthorized string
14 onNotFound string
15}
16
17type HandlerOption func(*Handler)
18
19// NewHandler creates a new Handler with defaults of /error, /login, and /404 unless overridden by options.
20func NewHandler(opts ...HandlerOption) *Handler {
21 eh := &Handler{
22 onError: "/error",
23 onUnauthorized: "/login",
24 onNotFound: "/404",
25 }
26 for _, opt := range opts {
27 opt(eh)
28 }
29 return eh
30}
31
32// WithOnError sets the redirect path for error errors.
33func WithOnError(onError string) HandlerOption {
34 return func(eh *Handler) {
35 eh.onError = onError
36 }
37}
38
39// WithOnUnauthorized sets the redirect path for unauthorized errors.
40func WithOnUnauthorized(onUnauthorized string) HandlerOption {
41 return func(eh *Handler) {
42 eh.onUnauthorized = onUnauthorized
43 }
44}
45
46// WithOnNotFound sets the redirect path for not found errors.
47func WithOnNotFound(onNotFound string) HandlerOption {
48 return func(eh *Handler) {
49 eh.onNotFound = onNotFound
50 }
51}
52
53func (eh *Handler) Handle(status int, w http.ResponseWriter, r *http.Request, errs ...error) {
54 l := log.FromContext(r.Context())
55 for _, err := range errs {
56 l.Error(err.Error(), "status", status)
57 }
58 // check if a wrapped response writer and status is still pending
59 ww, ok := w.(middleware.WrapResponseWriter)
60 if ok && ww.Status() == 0 {
61 return
62 }
63 // prevent accidental footguns
64 if status >= 200 && status < 300 {
65 return
66 }
67 switch status {
68 case http.StatusUnauthorized, http.StatusForbidden:
69 http.Redirect(w, r, eh.onUnauthorized, http.StatusFound)
70 case http.StatusNotFound:
71 http.Redirect(w, r, eh.onNotFound, http.StatusFound)
72 default:
73 http.Redirect(w, r, eh.onError, http.StatusFound)
74 }
75}
76
77func (eh *Handler) Redirect(path string, w http.ResponseWriter, r *http.Request, query ...string) {
78 if (len(query)%2) != 0 || len(query) == 0 {
79 if r.Header.Get("HX-Request") == "true" {
80 w.Header().Set("HX-Redirect", path)
81 w.WriteHeader(http.StatusOK)
82 return
83 }
84 http.Redirect(w, r, path, http.StatusFound)
85 return
86 }
87 v := url.Values{}
88 for i := 0; i < len(query); i += 2 {
89 v.Set(query[i], query[i+1])
90 }
91 if r.Header.Get("HX-Request") == "true" {
92 w.Header().Set("HX-Redirect", path+"?"+v.Encode())
93 w.WriteHeader(http.StatusOK)
94 return
95 }
96 http.Redirect(w, r, path+"?"+v.Encode(), http.StatusFound)
97}