package hws import ( "context" "io" "net/http" "net/http/httptest" "github.com/pkg/errors" ) // Error to use with Server.ThrowError 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(errorCode int) (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 } // TODO: add test for ErrorPageFunc that returns an error func (server *Server) AddErrorPage(pageFunc ErrorPageFunc) error { rr := httptest.NewRecorder() req := httptest.NewRequest("GET", "/", nil) page, err := pageFunc(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") } server.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 (server *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 !server.IsReady() { return errors.New("ThrowError called before server started") } w.WriteHeader(error.StatusCode) server.LogError(error) if server.errorPage == nil { server.LogError(HWSError{Message: "No error page provided", Error: nil, Level: ErrorDEBUG}) return nil } if error.RenderErrorPage { server.LogError(HWSError{Message: "Error page rendering", Error: nil, Level: ErrorDEBUG}) errPage, err := server.errorPage(error.StatusCode) if err != nil { server.LogError(HWSError{Message: "Failed to get a valid error page", Error: err}) } err = errPage.Render(r.Context(), w) if err != nil { server.LogError(HWSError{Message: "Failed to render error page", Error: err}) } } else { server.LogError(HWSError{Message: "Error page specified not to render", Error: nil, Level: ErrorDEBUG}) } return nil } func (server *Server) ThrowFatal(w http.ResponseWriter, err error) { w.WriteHeader(http.StatusInternalServerError) server.LogFatal(err) }