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 }