From 046e0f925971e9c4a031b2e8f90d0a114a4309f9 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Wed, 24 Jul 2024 15:23:07 -0300 Subject: [PATCH] refactor(errors): new error "helpers", following a more golang way --- internals/router/errors/400.go | 20 +++++++ internals/router/errors/500.go | 15 +++++ internals/router/errors/errors.templ | 90 ++++++++++++++++++++++++++++ routes/index.templ | 7 ++- routes/robots.go | 38 ++++++------ 5 files changed, 152 insertions(+), 18 deletions(-) create mode 100644 internals/router/errors/400.go create mode 100644 internals/router/errors/500.go create mode 100644 internals/router/errors/errors.templ diff --git a/internals/router/errors/400.go b/internals/router/errors/400.go new file mode 100644 index 0000000..9ecd8d0 --- /dev/null +++ b/internals/router/errors/400.go @@ -0,0 +1,20 @@ +package errors + +import ( + "fmt" + "net/http" + "strings" +) + +type ErrMissingParams struct { + defaultErr + Params []string `json:"params"` +} + +func NewErrMissingParams(params ...string) ErrMissingParams { + return ErrMissingParams{Params: params} +} +func (e ErrMissingParams) Error() string { + return fmt.Sprintf("Missing parameters: %s.", strings.Join(e.Params, ", ")) +} +func (e ErrMissingParams) Status() int { return http.StatusBadRequest } diff --git a/internals/router/errors/500.go b/internals/router/errors/500.go new file mode 100644 index 0000000..233e916 --- /dev/null +++ b/internals/router/errors/500.go @@ -0,0 +1,15 @@ +package errors + +import ( + "errors" + "net/http" +) + +type ErrInternal struct { + defaultErr + Err string `json:"error"` +} + +func NewErrInternal(err ...error) ErrInternal { return ErrInternal{Err: errors.Join(err...).Error()} } +func (e ErrInternal) Error() string { return e.Err } +func (e ErrInternal) Status() int { return http.StatusInternalServerError } diff --git a/internals/router/errors/errors.templ b/internals/router/errors/errors.templ new file mode 100644 index 0000000..530db45 --- /dev/null +++ b/internals/router/errors/errors.templ @@ -0,0 +1,90 @@ +package errors + +import ( + "net/http" + "strings" + "extrovert/templates/layouts" + "encoding/json" +) + +type Error interface { + Error() string + Status() int + ServeHTTP(w http.ResponseWriter, r *http.Request) + Component() templ.Component + JSON() string +} + +type defaultErr struct{} + +func (e defaultErr) Error() string { + return "Error: This method should have been overridden :')" +} + +func (e defaultErr) Status() int { + return http.StatusNotImplemented +} + +func (e defaultErr) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.Header.Get("Accept"), "text/html") { + w.Header().Set("Content-Type", "text/html") + + err := e.Component().Render(r.Context(), w) + if err != nil { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("Unable to render error message, using JSON representation: " + e.JSON())) + w.WriteHeader(http.StatusInternalServerError) + return + } + } else { + w.Header().Set("Content-Type", "application/json") + + _, err := w.Write([]byte(e.JSON())) + if err != nil { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("Unable to send error information due to: " + err.Error())) + w.WriteHeader(http.StatusInternalServerError) + return + } + } + w.WriteHeader(e.Status()) +} + +func (e defaultErr) JSON() string { + type jsonErr struct { + Error string `json:"error"` + Info any `json:"info"` + } + js, err := json.Marshal(jsonErr{ + Error: e.Error(), + Info: e, + }) + if err != nil { + js, _ = json.Marshal(jsonErr{ + Error: "Unable to parse JSON of error", + Info: err.Error(), + }) + } + + return string(js) +} + +templ (e defaultErr) Component() { + @layouts.Page("Error") { + + + + } +} diff --git a/routes/index.templ b/routes/index.templ index 05ecfd6..3f55cce 100644 --- a/routes/index.templ +++ b/routes/index.templ @@ -4,13 +4,18 @@ import ( "extrovert/templates/layouts" "extrovert/components" "net/http" + "extrovert/internals/router/errors" + e "errors" ) type Homepage struct{} func (h Homepage) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - _ = h.page().Render(context.Background(), w) + err := h.page().Render(context.Background(), w) + if err != nil { + errors.NewErrInternal(e.New("Unable to render dashboard"), err).ServeHTTP(w, r) + } } templ (h Homepage) page() { diff --git a/routes/robots.go b/routes/robots.go index dca05df..bc9b359 100644 --- a/routes/robots.go +++ b/routes/robots.go @@ -1,10 +1,11 @@ package routes import ( + e "errors" "io" "net/http" - "extrovert/internals" + "extrovert/internals/router/errors" ) type AITxt struct{} @@ -13,23 +14,24 @@ func (_ AITxt) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Add("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400") w.Header().Add("CDN-Cache-Control", "max-age=604800") - error := internals.HttpErrorHelper(w) - - aiList, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/ai.txt") - if error("Error trying to create ai block list", err, http.StatusInternalServerError) { + list, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/ai.txt") + if err != nil { + errors.NewErrInternal(e.New("Unable to fetch ai.txt list"), err).ServeHTTP(w, r) return } - bytes, err := io.ReadAll(aiList.Body) - if error("Error trying to create ai block list", err, http.StatusInternalServerError) { - return - } - _, err = io.WriteString(w, string(bytes)) - if error("Error trying to create ai block list", err, http.StatusInternalServerError) { + bytes, err := io.ReadAll(list.Body) + if err != nil { + errors.NewErrInternal(e.New("Unable to read dynamic ai.txt list"), err).ServeHTTP(w, r) return } w.Header().Add("Content-Type", "text/plain") + _, err = w.Write(bytes) + if err != nil { + errors.NewErrInternal(e.New("Unable to write ai.txt list"), err).ServeHTTP(w, r) + return + } } type RobotsTxt struct{} @@ -38,19 +40,21 @@ func (_ RobotsTxt) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Add("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400") w.Header().Add("CDN-Cache-Control", "max-age=604800") - error := internals.HttpErrorHelper(w) - aiList, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/robots.txt") - if error("Error trying to create robots block list", err, http.StatusInternalServerError) { + list, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/robots.txt") + if err != nil { + errors.NewErrInternal(e.New("Unable to fetch robots.txt list"), err).ServeHTTP(w, r) return } - bytes, err := io.ReadAll(aiList.Body) - if error("Error trying to create robots block list", err, http.StatusInternalServerError) { + bytes, err := io.ReadAll(list.Body) + if err != nil { + errors.NewErrInternal(e.New("Unable to read dynamic robots.txt list"), err).ServeHTTP(w, r) return } _, err = io.WriteString(w, string(bytes)) - if error("Error trying to create robots block list", err, http.StatusInternalServerError) { + if err != nil { + errors.NewErrInternal(e.New("Unable to write robots.txt list"), err).ServeHTTP(w, r) return } w.Header().Add("Content-Type", "text/plain")