-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpage_handler.go
More file actions
143 lines (113 loc) · 3.04 KB
/
page_handler.go
File metadata and controls
143 lines (113 loc) · 3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package pages
import (
"context"
"fmt"
"log/slog"
"net/http"
"github.com/gowool/keratin"
)
var _ keratin.Handler = (*PageHandler)(nil)
// Theme defines an interface for rendering templates with a specific context and writing the output to an io.Writer.
type Theme interface {
Render(ctx context.Context, template string, data any) ([]byte, error)
}
// TemplateVarsFunc return a template variables.
type TemplateVarsFunc func(*http.Request, *Context) any
type TemplateVars struct {
*Context
Request *http.Request
Ctx context.Context
}
func (c TemplateVars) Value(key any) any {
return c.Request.Context().Value(key)
}
type PageHandlerConfig struct {
// VarsFunc defines a function that generates
// template variables from an HTTP request and context.
VarsFunc TemplateVarsFunc
// Logger is used for logging.
Logger *slog.Logger
}
func (c *PageHandlerConfig) SetDefaults() {
if c.VarsFunc == nil {
c.VarsFunc = c.vars
}
if c.Logger == nil {
c.Logger = slog.Default()
}
c.Logger = c.Logger.WithGroup("page_handler")
}
func (*PageHandlerConfig) vars(r *http.Request, c *Context) any {
return TemplateVars{
Context: c,
Request: r,
Ctx: r.Context(),
}
}
// PageHandler renders pages using a Theme.
type PageHandler struct {
theme Theme
varsFunc TemplateVarsFunc
logger *slog.Logger
}
func NewPageHandler(theme Theme) *PageHandler {
return NewPageHandlerWithConfig(theme, PageHandlerConfig{})
}
func NewPageHandlerWithConfig(theme Theme, cfg PageHandlerConfig) *PageHandler {
if theme == nil {
panic("page handler: theme is required")
}
cfg.SetDefaults()
return &PageHandler{
theme: theme,
varsFunc: cfg.VarsFunc,
logger: cfg.Logger,
}
}
func (h *PageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
c := MustContext(r.Context())
if !c.HasError() {
if !c.HasSite() {
return fmt.Errorf("page handler: %w", ErrSiteNotFound)
}
if !c.HasPage() {
return fmt.Errorf("page handler: %w", ErrPageNotFound)
}
}
if c.HasPage() {
for key, values := range c.Page().Header {
for i, value := range values {
if i == 0 {
w.Header().Set(key, value)
} else {
w.Header().Add(key, value)
}
}
}
}
if !c.HasTemplate() {
if !c.HasError() {
return fmt.Errorf("page handler: %w", ErrTemplateEmpty)
}
h.logger.ErrorContext(r.Context(), "template empty", "context_error", c.Error())
http.Error(w, http.StatusText(c.Status()), c.Status())
return nil
}
data, err := h.theme.Render(r.Context(), c.Template(), h.varsFunc(r, c))
if err != nil {
if !c.HasError() {
return fmt.Errorf("page handler: theme write error: %w", err)
}
h.logger.ErrorContext(r.Context(), "theme write error", "error", err, "context_error", c.Error())
http.Error(w, http.StatusText(c.Status()), c.Status())
return nil
}
ct := w.Header().Get(keratin.HeaderContentType)
if ct == "" {
ct = keratin.MIMETextHTMLCharsetUTF8
}
if err = keratin.Blob(w, c.Status(), ct, data); err != nil {
h.logger.ErrorContext(r.Context(), "write response error", "error", err)
}
return nil
}