Skip to content

Commit 5ae41f0

Browse files
committed
handler with error handler
1 parent 8020b8c commit 5ae41f0

File tree

3 files changed

+101
-16
lines changed

3 files changed

+101
-16
lines changed

error_renderable.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ type ErrorRenderable interface {
1717
ErrorRenderable(ctx context.Context, err error) (AsRenderable, error)
1818
}
1919

20+
type ErrorRenderableFunc func(context.Context, error) (AsRenderable, error)
21+
22+
func (f ErrorRenderableFunc) ErrorRenderable(ctx context.Context, err error) (AsRenderable, error) {
23+
return f(ctx, err)
24+
}
25+
2026
func handleRenderError(ctx context.Context, err error, with any) (template.HTML, error) {
2127
var empty template.HTML
2228

http_request_renderable.go

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package veun
22

33
import (
4+
"context"
5+
"log/slog"
46
"net/http"
57
)
68

@@ -20,29 +22,54 @@ func (f RequestRenderableFunc) RequestRenderable(r *http.Request) (AsRenderable,
2022
return f(r)
2123
}
2224

23-
func HTTPHandlerFunc(r RequestRenderableFunc) http.Handler {
24-
return handler{Renderable: r}
25+
func HTTPHandlerFunc(r RequestRenderableFunc, opts ...HandlerOption) http.Handler {
26+
h := handler{Renderable: r}
27+
for _, opt := range opts {
28+
opt(&h)
29+
}
30+
return h
31+
}
32+
33+
func HTTPHandler(r RequestRenderable, opts ...HandlerOption) http.Handler {
34+
h := handler{Renderable: r}
35+
for _, opt := range opts {
36+
opt(&h)
37+
}
38+
return h
2539
}
2640

27-
func HTTPHandler(r RequestRenderable) http.Handler {
28-
return handler{Renderable: r}
41+
type HandlerOption func(h *handler)
42+
43+
func WithErrorHandler(eh ErrorRenderable) HandlerOption {
44+
return func(h *handler) {
45+
h.ErrorHandler = eh
46+
}
47+
}
48+
49+
func WithErrorHandlerFunc(eh ErrorRenderableFunc) HandlerOption {
50+
return func(h *handler) {
51+
h.ErrorHandler = eh
52+
}
2953
}
3054

3155
// handler implements http.Handler for a RequestRenderable.
3256
type handler struct {
33-
Renderable RequestRenderable
57+
Renderable RequestRenderable
58+
ErrorHandler ErrorRenderable
3459
}
3560

3661
// ServeHTTP implements http.Handler.
3762
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3863
renderable, next, err := h.Renderable.RequestRenderable(r)
3964
if err != nil {
40-
panic(err)
65+
h.handleError(r.Context(), w, err)
66+
return
4167
}
4268

4369
html, err := Render(r.Context(), renderable)
4470
if err != nil {
45-
panic(err)
71+
h.handleError(r.Context(), w, err)
72+
return
4673
}
4774

4875
if next != nil {
@@ -54,3 +81,17 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5481
panic(err)
5582
}
5683
}
84+
85+
func (h handler) handleError(ctx context.Context, w http.ResponseWriter, err error) {
86+
html, rErr := handleRenderError(ctx, err, h.ErrorHandler)
87+
if rErr == nil && len(html) > 0 {
88+
w.WriteHeader(http.StatusInternalServerError)
89+
_, _ = w.Write([]byte(html))
90+
return
91+
}
92+
93+
// TODO: grab the logger from the context
94+
slog.Error("handler failed", "err", err)
95+
code := http.StatusInternalServerError
96+
http.Error(w, http.StatusText(code), code)
97+
}

http_request_test.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,31 @@ func HTML(renderable RequestRenderable) http.Handler {
2626
return HTTPHandlerFunc(func(r *http.Request) (AsRenderable, http.Handler, error) {
2727
v, next, err := renderable.RequestRenderable(r)
2828
if err != nil {
29-
return nil, next, err
29+
return nil, nil, err
30+
} else if v == nil {
31+
return nil, next, nil
3032
}
3133

3234
return html{Body: v}, next, nil
3335
})
3436
}
3537

38+
var errorViewTpl = MustParseTemplate("errorView", `Error: {{ . }}`)
39+
40+
type errorView struct {
41+
Error error
42+
}
43+
44+
func (v errorView) Renderable(_ context.Context) (Renderable, error) {
45+
return View{Tpl: errorViewTpl, Data: v.Error}, nil
46+
}
47+
48+
func newErrorView(_ context.Context, err error) (AsRenderable, error) {
49+
return errorView{Error: err}, nil
50+
}
51+
3652
func TestRequestRequestHandler(t *testing.T) {
53+
3754
var statusCode = func(code int) http.Handler {
3855
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3956
w.WriteHeader(code)
@@ -51,19 +68,22 @@ func TestRequestRequestHandler(t *testing.T) {
5168
}
5269
})
5370

54-
mux := http.NewServeMux()
55-
56-
mux.Handle("/empty", HTTPHandler(empty))
57-
mux.Handle("/html/empty", HTML(empty))
58-
59-
mux.Handle("/person", HTTPHandlerFunc(func(r *http.Request) (AsRenderable, http.Handler, error) {
71+
var person = RequestRenderableFunc(func(r *http.Request) (AsRenderable, http.Handler, error) {
6072
name := r.URL.Query().Get("name")
6173
if name == "" {
6274
return nil, nil, fmt.Errorf("missing name")
6375
}
6476

6577
return PersonView(Person{Name: name}), nil, nil
66-
}))
78+
})
79+
80+
mux := http.NewServeMux()
81+
82+
mux.Handle("/empty", HTTPHandler(empty))
83+
mux.Handle("/html/empty", HTML(empty))
84+
85+
mux.Handle("/person", HTTPHandler(person, WithErrorHandlerFunc(newErrorView)))
86+
mux.Handle("/html/person", HTML(person))
6787

6888
server := httptest.NewServer(mux)
6989
defer server.Close()
@@ -116,6 +136,12 @@ func TestRequestRequestHandler(t *testing.T) {
116136
assert.Equal(t, 200, code)
117137
})
118138

139+
t.Run("person (name=)", func(t *testing.T) {
140+
body, code, _ := sendRequest(t, "/person?name=")
141+
assert.Equal(t, 500, code)
142+
assert.Equal(t, "Error: missing name", body)
143+
})
144+
119145
t.Run("person renders (name=someone)", func(t *testing.T) {
120146
body, code, _ := sendRequest(t, "/person?name=someone")
121147
assert.Equal(t, "<div>Hi, someone.</div>", body)
@@ -124,7 +150,19 @@ func TestRequestRequestHandler(t *testing.T) {
124150

125151
t.Run("/html/empty", func(t *testing.T) {
126152
body, code, _ := sendRequest(t, "/html/empty")
127-
assert.Equal(t, "<html><body></body></html>", body)
153+
assert.Equal(t, "", body)
128154
assert.Equal(t, 200, code)
129155
})
156+
157+
t.Run("/html/person (name=Stan)", func(t *testing.T) {
158+
body, code, _ := sendRequest(t, "/html/person?name=Stan")
159+
assert.Equal(t, "<html><body><div>Hi, Stan.</div></body></html>", body)
160+
assert.Equal(t, 200, code)
161+
})
162+
163+
t.Run("/html/person (name=)", func(t *testing.T) {
164+
body, code, _ := sendRequest(t, "/html/person?name=")
165+
assert.Equal(t, "Internal Server Error\n", body)
166+
assert.Equal(t, 500, code)
167+
})
130168
}

0 commit comments

Comments
 (0)