Files
golib/hws/errors.go
2026-02-03 18:43:31 +11:00

116 lines
3.7 KiB
Go

package hws
import (
"context"
"io"
"net/http"
"net/http/httptest"
"github.com/pkg/errors"
)
// HWSError wraps an error with other information for use with HWS features
type HWSError struct {
StatusCode int // HTTP Status code
Message string // Error message
Error error // Error
Level ErrorLevel // Error level to use for logging. Defaults to Error
RenderErrorPage bool // If true, the servers ErrorPage will be rendered
}
type ErrorLevel string
const (
ErrorDEBUG ErrorLevel = "Debug"
ErrorINFO ErrorLevel = "Info"
ErrorWARN ErrorLevel = "Warn"
ErrorERROR ErrorLevel = "Error"
ErrorFATAL ErrorLevel = "Fatal"
ErrorPANIC ErrorLevel = "Panic"
)
// ErrorPageFunc is a function that returns an ErrorPage with the specified HTTP Status code
// This will be called by the server when it needs to render an error page
type ErrorPageFunc func(error HWSError) (ErrorPage, error)
// ErrorPage must implement a Render() function that takes in a context and ResponseWriter,
// and should write a reponse as output to the ResponseWriter.
// Server.ThrowError will call the Render() function on the current request
type ErrorPage interface {
Render(ctx context.Context, w io.Writer) error
}
// AddErrorPage registers a handler that returns an ErrorPage
func (s *Server) AddErrorPage(pageFunc ErrorPageFunc) error {
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
page, err := pageFunc(HWSError{StatusCode: http.StatusInternalServerError})
if err != nil {
return errors.Wrap(err, "An error occured when trying to get the error page")
}
err = page.Render(req.Context(), rr)
if err != nil {
return errors.Wrap(err, "An error occured when trying to render the error page")
}
if len(rr.Header()) == 0 && rr.Body.String() == "" {
return errors.New("Render method of the error page did not write anything to the response writer")
}
s.errorPage = pageFunc
return nil
}
// ThrowError will write the HTTP status code to the response headers, and log
// the error with the level specified by the HWSError.
// If HWSError.RenderErrorPage is true, the error page will be rendered to the ResponseWriter
// and the request chain should be terminated.
func (s *Server) ThrowError(w http.ResponseWriter, r *http.Request, error HWSError) {
err := s.throwError(w, r, error)
if err != nil {
s.LogError(error)
s.LogError(HWSError{
Message: "Error occured during throwError",
Error: errors.Wrap(err, "s.throwError"),
Level: ErrorERROR,
})
}
}
func (s *Server) throwError(w http.ResponseWriter, r *http.Request, error HWSError) error {
if error.StatusCode <= 0 {
return errors.New("HWSError.StatusCode cannot be 0.")
}
if error.Message == "" {
return errors.New("HWSError.Message cannot be empty")
}
if error.Error == nil {
return errors.New("HWSError.Error cannot be nil")
}
if r == nil {
return errors.New("Request cannot be nil")
}
if !s.IsReady() {
return errors.New("ThrowError called before server started")
}
w.WriteHeader(error.StatusCode)
s.LogError(error)
if s.errorPage == nil {
s.LogError(HWSError{Message: "No error page provided", Error: nil, Level: ErrorDEBUG})
return nil
}
if error.RenderErrorPage {
s.LogError(HWSError{Message: "Error page rendering", Error: nil, Level: ErrorDEBUG})
errPage, err := s.errorPage(error)
if err != nil {
s.LogError(HWSError{Message: "Failed to get a valid error page", Error: err})
}
err = errPage.Render(r.Context(), w)
if err != nil {
s.LogError(HWSError{Message: "Failed to render error page", Error: err})
}
} else {
s.LogError(HWSError{Message: "Error page specified not to render", Error: nil, Level: ErrorDEBUG})
}
return nil
}