carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1package todo
2
3import (
4 "bytes"
5 "embed"
6 "html/template"
7 "net/http"
8 "strconv"
9
10 "amadan.net/rastrillo/rastrillo"
11)
12
13//go:embed templates static
14var appFS embed.FS
15
16// assets fingerprints static/: {{asset "static/index.css"}} returns a
17// content-hashed URL served immutable-cacheable, and the bare name
18// (/static/index.css) is served too, never long-cached.
19var assets = rastrillo.NewAssets(appFS)
20
21var templates = template.Must(template.New("").
22 Funcs(template.FuncMap{
23 "asset": assets.Path,
24 "plural": func(n int) string {
25 if n == 1 {
26 return "item"
27 }
28 return "items"
29 },
30 }).
31 ParseFS(appFS, "templates/*.html"))
32
33// render executes a template into a buffer before anything touches the
34// wire, so a template error becomes a clean 500 and the response
35// carries an exact Content-Length.
36func render(w http.ResponseWriter, name string, data any) {
37 renderStatus(w, http.StatusOK, name, data)
38}
39
40func renderStatus(w http.ResponseWriter, status int, name string, data any) {
41 var buf bytes.Buffer
42 if err := templates.ExecuteTemplate(&buf, name, data); err != nil {
43 http.Error(w, "something went wrong", http.StatusInternalServerError)
44 return
45 }
46 h := w.Header()
47 h.Set("Content-Type", "text/html; charset=utf-8")
48 h.Set("Content-Length", strconv.Itoa(buf.Len()))
49 w.WriteHeader(status)
50 buf.WriteTo(w)
51}
52
53// ErrorPage is the rastrillo.ErrorPageFunc Options.ErrorPage points at,
54// so a panic gets the same page a handled failure does: TodoMVC's frame
55// with the status and, for a 500, the ref the log line carries.
56func ErrorPage(w http.ResponseWriter, r *http.Request, status int, ref string) {
57 title := http.StatusText(status)
58 if status == http.StatusInternalServerError {
59 title = "Something went wrong on our side"
60 }
61 renderStatus(w, status, "errors", map[string]any{"Title": title, "Ref": ref})
62}
63
64func errorPage(w http.ResponseWriter, status int, title string) {
65 renderStatus(w, status, "errors", map[string]any{"Title": title})
66}
67