about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--frontend/static/style.css5
-rw-r--r--frontend/templates/blocks/error.gotmpl6
-rw-r--r--internal/server/error.go43
-rw-r--r--internal/server/mux.go30
4 files changed, 72 insertions, 12 deletions
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" }}
+  <p class="notice error">
+    {{ .Code }}
+    {{ .Message }}
+  </p>
+{{- 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
 			}