refactor(errors,middlwares): new error middleware implementation
This commit is contained in:
@@ -8,11 +8,7 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Middleware func(next http.Handler) http.Handler
|
||||
|
||||
type MiddlewareStruct interface {
|
||||
Wrap(next http.Handler) http.Handler
|
||||
}
|
||||
type Middleware = func(next http.Handler) http.Handler
|
||||
|
||||
type MiddlewaredReponse struct {
|
||||
w http.ResponseWriter
|
||||
@@ -89,10 +85,12 @@ func MultiResponseWriter(
|
||||
}
|
||||
|
||||
func (w *multiResponseWriter) WriteHeader(status int) {
|
||||
w.Header().Set("Status", strconv.Itoa(status))
|
||||
w.response.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (w *multiResponseWriter) Write(p []byte) (int, error) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
for _, w := range w.writers {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package rerrors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/router/middleware"
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
const (
|
||||
ERROR_MIDDLEWARE_HEADER = "XX-Error-Middleware"
|
||||
ERROR_VALUE_HEADER = "X-Error-Value"
|
||||
)
|
||||
|
||||
type RouteError struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Error string `json:"error"`
|
||||
@@ -45,11 +45,9 @@ func (rerr RouteError) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
rerr.Info = map[string]any{}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
j, err := json.Marshal(rerr)
|
||||
if err != nil {
|
||||
j, _ := json.Marshal(RouteError{
|
||||
j, _ = json.Marshal(RouteError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Error: "Failed to marshal error message to JSON",
|
||||
Info: map[string]any{
|
||||
@@ -57,13 +55,19 @@ func (rerr RouteError) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
"error": err.Error(),
|
||||
},
|
||||
})
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
if _, err = w.Write(j); err != nil {
|
||||
_, _ = w.Write([]byte("Failed to write error JSON string to body"))
|
||||
}
|
||||
}
|
||||
|
||||
if r.Header.Get(ERROR_MIDDLEWARE_HEADER) == "enable" && prefersHtml(r.Header) {
|
||||
q := r.URL.Query()
|
||||
q.Set("error", base64.URLEncoding.EncodeToString(j))
|
||||
r.URL.RawQuery = q.Encode()
|
||||
|
||||
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.WriteHeader(rerr.StatusCode)
|
||||
if _, err = w.Write(j); err != nil {
|
||||
_, _ = w.Write([]byte("Failed to write error JSON string to body"))
|
||||
@@ -72,124 +76,6 @@ func (rerr RouteError) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
type ErrorMiddlewarePage func(err RouteError) templ.Component
|
||||
|
||||
type ErrorMiddleware struct {
|
||||
page ErrorMiddlewarePage
|
||||
notfound ErrorMiddlewarePage
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func NewErrorMiddleware(
|
||||
p ErrorMiddlewarePage,
|
||||
l *slog.Logger,
|
||||
notfound ...ErrorMiddlewarePage,
|
||||
) *ErrorMiddleware {
|
||||
var nf ErrorMiddlewarePage
|
||||
if len(notfound) > 0 {
|
||||
nf = notfound[0]
|
||||
} else {
|
||||
nf = p
|
||||
}
|
||||
|
||||
l = l.WithGroup("error_middleware")
|
||||
|
||||
return &ErrorMiddleware{p, nf, l}
|
||||
}
|
||||
|
||||
func (m *ErrorMiddleware) Wrap(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if uerr := r.URL.Query().Get("error"); uerr != "" {
|
||||
ErrorDisplayer{m.log, m.page}.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
mw := middleware.MultiResponseWriter(w, &buf)
|
||||
|
||||
next.ServeHTTP(mw, r)
|
||||
|
||||
if mw.Header().Get("Status") == "" {
|
||||
m.log.Warn("Endpoint did not return a Status code",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.String("status", mw.Header().Get("Status")),
|
||||
slog.Any("data", buf),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
status, err := strconv.Atoi(mw.Header().Get("Status"))
|
||||
if err != nil {
|
||||
m.log.Warn("Failed to parse Status code to a integer",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.String("status", mw.Header().Get("Status")),
|
||||
slog.String("data", err.Error()),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if status < 400 {
|
||||
return
|
||||
} else if status == 404 {
|
||||
rerr := NotFound()
|
||||
|
||||
w.WriteHeader(rerr.StatusCode)
|
||||
|
||||
b, err := json.Marshal(rerr)
|
||||
if err != nil {
|
||||
_, _ = w.Write([]byte(
|
||||
fmt.Sprintf("%#v", rerr),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
if prefersHtml(r.Header) {
|
||||
u, _ := url.Parse(r.URL.String())
|
||||
|
||||
q := u.Query()
|
||||
q.Add("error", base64.URLEncoding.EncodeToString(b))
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(&buf)
|
||||
if err != nil {
|
||||
m.log.Error("Failed to read from error body",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.String("status", mw.Header().Get("Status")),
|
||||
slog.Any("data", buf),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if mw.Header().Get("Content-Type") != "application/json" {
|
||||
m.log.Warn("Endpoint didn't return a structured error",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.String("status", mw.Header().Get("Status")),
|
||||
slog.String("data", string(body)),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if prefersHtml(r.Header) {
|
||||
u, _ := url.Parse(r.URL.String())
|
||||
|
||||
q := u.Query()
|
||||
q.Add("error", base64.URLEncoding.EncodeToString(body))
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type ErrorDisplayer struct {
|
||||
log *slog.Logger
|
||||
page ErrorMiddlewarePage
|
||||
@@ -232,6 +118,42 @@ func (h ErrorDisplayer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
type ErrorMiddleware struct {
|
||||
page ErrorMiddlewarePage
|
||||
notfound ErrorMiddlewarePage
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func NewErrorMiddleware(
|
||||
p ErrorMiddlewarePage,
|
||||
l *slog.Logger,
|
||||
notfound ...ErrorMiddlewarePage,
|
||||
) *ErrorMiddleware {
|
||||
var nf ErrorMiddlewarePage
|
||||
if len(notfound) > 0 {
|
||||
nf = notfound[0]
|
||||
} else {
|
||||
nf = p
|
||||
}
|
||||
|
||||
l = l.WithGroup("error_middleware")
|
||||
|
||||
return &ErrorMiddleware{p, nf, l}
|
||||
}
|
||||
|
||||
func (m *ErrorMiddleware) Wrap(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Header.Set(ERROR_MIDDLEWARE_HEADER, "enable")
|
||||
|
||||
if uerr := r.URL.Query().Get("error"); uerr != "" && prefersHtml(r.Header) {
|
||||
ErrorDisplayer{m.log, m.page}.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func prefersHtml(h http.Header) bool {
|
||||
if h.Get("Accept") == "" {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user