|
| 1 | +package veun |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "log/slog" |
| 6 | + "net/http" |
| 7 | +) |
| 8 | + |
| 9 | +// HTTPHandler constructs an http.HTTPHandler given the RequestRenderable. |
| 10 | +func HTTPHandler(r RequestRenderable, opts ...HandlerOption) http.Handler { |
| 11 | + h := handler{Renderable: r} |
| 12 | + for _, opt := range opts { |
| 13 | + opt(&h) |
| 14 | + } |
| 15 | + return h |
| 16 | +} |
| 17 | + |
| 18 | +// HTTPHandler constructs an http.HTTPHandler given the RequestRenderableFunc. |
| 19 | +func HTTPHandlerFunc(r RequestRenderableFunc, opts ...HandlerOption) http.Handler { |
| 20 | + h := handler{Renderable: r} |
| 21 | + for _, opt := range opts { |
| 22 | + opt(&h) |
| 23 | + } |
| 24 | + return h |
| 25 | +} |
| 26 | + |
| 27 | +// HandlerOption is an option that can be provided to the handler. |
| 28 | +type HandlerOption func(h *handler) |
| 29 | + |
| 30 | +// WithErrorHandler creates a HandlerOption that can be provided to HTTPHandler |
| 31 | +// or HTTPHandlerFunc. |
| 32 | +// |
| 33 | +// This can change the default error handling behavior of the handler. |
| 34 | +func WithErrorHandler(eh ErrorRenderable) HandlerOption { |
| 35 | + return func(h *handler) { |
| 36 | + h.ErrorHandler = eh |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +// WithErrorHandlerFunc is the same as WithErrorHandler. |
| 41 | +func WithErrorHandlerFunc(eh ErrorRenderableFunc) HandlerOption { |
| 42 | + return WithErrorHandler(eh) |
| 43 | +} |
| 44 | + |
| 45 | +// handler implements http.Handler for a RequestRenderable. |
| 46 | +type handler struct { |
| 47 | + Renderable RequestRenderable |
| 48 | + ErrorHandler ErrorRenderable |
| 49 | +} |
| 50 | + |
| 51 | +// ServeHTTP implements http.Handler. |
| 52 | +func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 53 | + renderable, next, err := h.Renderable.RequestRenderable(r) |
| 54 | + if err != nil { |
| 55 | + h.handleError(r.Context(), w, err) |
| 56 | + return |
| 57 | + } |
| 58 | + |
| 59 | + html, err := Render(r.Context(), renderable) |
| 60 | + if err != nil { |
| 61 | + h.handleError(r.Context(), w, err) |
| 62 | + return |
| 63 | + } |
| 64 | + |
| 65 | + if next != nil { |
| 66 | + next.ServeHTTP(w, r) |
| 67 | + } |
| 68 | + |
| 69 | + _, err = w.Write([]byte(html)) |
| 70 | + if err != nil { |
| 71 | + panic(err) |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +func (h handler) handleError(ctx context.Context, w http.ResponseWriter, err error) { |
| 76 | + html, rErr := handleRenderError(ctx, err, h.ErrorHandler) |
| 77 | + if rErr == nil && len(html) > 0 { |
| 78 | + w.WriteHeader(http.StatusInternalServerError) |
| 79 | + _, _ = w.Write([]byte(html)) |
| 80 | + return |
| 81 | + } |
| 82 | + |
| 83 | + // TODO: grab the logger from the context |
| 84 | + slog.Error("handler failed", "err", err) |
| 85 | + code := http.StatusInternalServerError |
| 86 | + http.Error(w, http.StatusText(code), code) |
| 87 | +} |
0 commit comments