This is how I handle error with golang on http server
package e import ( "errors" "fmt" "net/http" "runtime" ) // Error implements built in error with status code, caller, and message attribute type Error struct { Err error statusCode int message string caller string } // New wraps err with defined statusCode and message func New(err error, statusCode int, message string) error { return &Error{ Err: err, statusCode: statusCode, message: message, caller: getCaller(), } } // Wrap wraps err with custom message // Wrap's result inherit statusCode from err if err equals *Error func Wrap(err error, msg string) error { var e *Error statusCode := http.StatusInternalServerError if errors.As(err, &e) { statusCode = e.statusCode } return &Error{ Err: err, statusCode: statusCode, message: msg, caller: getCaller(), } } // From creates new error from defined statusCode // if statusCode doesn't have any status text // statusCode changed to http.StatusInternalServerError func From(statusCode int) error { text := http.StatusText(statusCode) if text == "" { text = http.StatusText(http.StatusInternalServerError) statusCode = http.StatusInternalServerError } return &Error{ Err: errors.New(text), statusCode: statusCode, message: "", caller: getCaller(), } } // Error returns error message with caller func (e Error) Error() string { if e.message == "" { return e.Err.Error() } return fmt.Sprintf("%v\n[%v] > %v", e.Err, e.caller, e.message) } // Unwrap enables errors.As and errors.Is func (e Error) Unwrap() error { return e.Err } // StatusCode returns e.statusCode func (e Error) StatusCode() int { return e.statusCode } // getCaller uses log.Lshortfile to format the caller func getCaller() string { _, file, line, ok := runtime.Caller(2) if !ok { file = "???" line = 0 } short := file for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { short = file[i+1:] break } } file = short return fmt.Sprintf("%s:%d", file, line) }
Usage:
package main import ( "database/sql" "errors" "fmt" "e" ) func main() { base := e.New(sql.ErrNoRows, 404, "user not found") base2 := e.Wrap(base, "bleh") concrete := e.Wrap(base2, "there's no user with such credentials") fmt.Println(concrete) fmt.Println(errors.Is(concrete, base)) var ee *Error if errors.As(concrete, &ee) { fmt.Println(ee.StatusCode()) } }
Result:
sql: no rows in result set [main.go:11] > user not found [main.go:12] > bleh [main.go:13] > there's no user with such credentials true 404
Top comments (0)