From 2d5ee7b69cbe27e7e1abef7bf3451cb6455c4387 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 15 May 2024 12:44:03 +0200 Subject: feat: render HTML error pages --- frontend/static/style.css | 5 ++++ frontend/templates/blocks/error.gotmpl | 6 +++++ internal/server/error.go | 43 ++++++++++++++++++++++++++++++++++ internal/server/mux.go | 30 ++++++++++++++---------- 4 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 frontend/templates/blocks/error.gotmpl create mode 100644 internal/server/error.go diff --git a/frontend/static/style.css b/frontend/static/style.css index 8f93079..9dbe198 100644 --- a/frontend/static/style.css +++ b/frontend/static/style.css @@ -2,6 +2,7 @@ --standard-border-radius: 0; --preformatted: var(--code); --min-width: 60rem; + --accent-error: #ffe0e0; } @media (prefers-color-scheme: light) { @@ -109,3 +110,7 @@ section { text-align: center; } } + +.error { + background: var(--accent-error); +} diff --git a/frontend/templates/blocks/error.gotmpl b/frontend/templates/blocks/error.gotmpl new file mode 100644 index 0000000..1352b2e --- /dev/null +++ b/frontend/templates/blocks/error.gotmpl @@ -0,0 +1,6 @@ +{{- define "main" }} +
+ {{ .Code }} + {{ .Message }} +
+{{- end }} diff --git a/internal/server/error.go b/internal/server/error.go new file mode 100644 index 0000000..51453f4 --- /dev/null +++ b/internal/server/error.go @@ -0,0 +1,43 @@ +package server + +import ( + "log/slog" + "net/http" + "searchix/internal/config" +) + +func createErrorHandler( + config *config.Config, +) func(http.ResponseWriter, *http.Request, string, int) { + return func(w http.ResponseWriter, r *http.Request, message string, code int) { + var err error + if message == "" { + message = http.StatusText(code) + } + indexData := TemplateData{ + ExtraHeadHTML: config.Web.ExtraHeadHTML, + Sources: config.Importer.Sources, + Version: *versionInfo, + Code: code, + Message: message, + } + w.WriteHeader(code) + if r.Header.Get("Fetch") == "true" { + err = templates["error"].ExecuteTemplate(w, "main", indexData) + } else { + err = templates["error"].Execute(w, indexData) + } + if err != nil { + slog.Error( + "error rendering error page template", + "error", + err, + "code", + code, + "message", + message, + ) + http.Error(w, message, code) + } + } +} diff --git a/internal/server/mux.go b/internal/server/mux.go index aa2c1a6..c1f36fe 100644 --- a/internal/server/mux.go +++ b/internal/server/mux.go @@ -48,6 +48,8 @@ type TemplateData struct { SourceResult *bleve.SearchResult ExtraHeadHTML template.HTML Version VersionInfo + Code int + Message string } type ResultData[T options.NixOption] struct { @@ -64,6 +66,8 @@ var versionInfo = &VersionInfo{ CommitSHA: config.CommitSHA, } +var templates TemplateCollection + func applyDevModeOverrides(config *config.Config) { if len(config.Web.ContentSecurityPolicy.ScriptSrc) == 0 { config.Web.ContentSecurityPolicy.ScriptSrc = config.Web.ContentSecurityPolicy.DefaultSrc @@ -93,14 +97,16 @@ func NewMux( Repanic: true, }) - templates, err := loadTemplates() + templates, err = loadTemplates() if err != nil { log.Panicf("could not load templates: %v", err) } + errorHandler := createErrorHandler(config) + top := http.NewServeMux() mux := http.NewServeMux() - mux.HandleFunc("/{$}", func(w http.ResponseWriter, _ *http.Request) { + mux.HandleFunc("/{$}", func(w http.ResponseWriter, r *http.Request) { indexData := TemplateData{ ExtraHeadHTML: config.Web.ExtraHeadHTML, Sources: config.Importer.Sources, @@ -108,7 +114,7 @@ func NewMux( } err := templates["index"].Execute(w, indexData) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) } }) @@ -118,7 +124,7 @@ func NewMux( source := config.Importer.Sources[sourceKey] if source == nil { - http.Error(w, "Source not found", http.StatusNotFound) + errorHandler(w, r, "Source not found", http.StatusNotFound) return } @@ -133,18 +139,18 @@ func NewMux( if pg != "" { page, err = strconv.ParseUint(pg, 10, 64) if err != nil || page == 0 { - http.Error(w, "Bad query string", http.StatusBadRequest) + errorHandler(w, r, "Bad query string", http.StatusBadRequest) } } results, err := index.Search(ctx, sourceKey, qs, (page-1)*search.ResultsPerPage) if err != nil { if err == context.DeadlineExceeded { - http.Error(w, "Search timed out", http.StatusInternalServerError) + errorHandler(w, r, "Search timed out", http.StatusInternalServerError) return } slog.Error("search error", "error", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) } tdata := ResultData[options.NixOption]{ @@ -163,13 +169,13 @@ func NewMux( if results.Total > hits { q, err := url.ParseQuery(r.URL.RawQuery) if err != nil { - http.Error(w, "Query string error", http.StatusBadRequest) + errorHandler(w, r, "Query string error", http.StatusBadRequest) return } if page*search.ResultsPerPage > results.Total { - http.Error(w, "Not found", http.StatusNotFound) + errorHandler(w, r, "Not found", http.StatusNotFound) return } @@ -198,12 +204,12 @@ func NewMux( } if err != nil { slog.Error("template error", "template", "options", "error", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) } } else { sourceResult, err := index.GetSource(ctx, sourceKey) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) return } @@ -216,7 +222,7 @@ func NewMux( Version: *versionInfo, }) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) return } -- cgit 1.4.1