| package main |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "os" |
| "reflect" |
| "strings" |
| |
| "github.com/julienschmidt/httprouter" |
| ) |
| |
| |
| |
| func _wrapError(msg string, err error) error { |
| return fmt.Errorf("%s: %w", msg, err) |
| } |
| |
| func GetErrorMessageTitle(err error) string { |
| return strings.SplitN(err.Error(), ":", 2)[0] |
| } |
| |
| |
| |
| type ApiError struct { |
| statusCode int |
| msg string |
| err error |
| } |
| |
| func NewApiError(statusCode int, msg string, err error) error { |
| if err != nil { |
| msg = fmt.Sprintf("%s: %s", msg, err.Error()) |
| } |
| return &ApiError{statusCode: statusCode, msg: msg, err: err} |
| } |
| |
| func (e *ApiError) Error() string { |
| return e.msg |
| } |
| |
| func (e *ApiError) Unwrap() error { |
| return e.err |
| } |
| |
| func (e *ApiError) StatusCode() int { |
| return e.statusCode |
| } |
| |
| func ExtractErrorMessageChain(err error) string { |
| e := err |
| var b strings.Builder |
| for { |
| b.WriteString(e.Error() + "\n") |
| e = errors.Unwrap(e) |
| if e == nil { |
| break |
| } |
| } |
| return b.String() |
| } |
| |
| func IsErrorEqual(e1 error, e2 error) bool { |
| if e1 == nil || e2 == nil { |
| return e1 == e2 |
| } else { |
| return reflect.TypeOf(e1) == reflect.TypeOf(e2) && GetErrorMessageTitle(e1) == GetErrorMessageTitle(e2) |
| } |
| } |
| |
| func WrapError(template error, err error) error { |
| apiErrorTemplate, ok := template.(*ApiError) |
| if ok { |
| err = NewApiError(apiErrorTemplate.StatusCode(), apiErrorTemplate.Error(), err) |
| } else { |
| err = _wrapError(template.Error(), err) |
| } |
| return err |
| } |
| |
| |
| |
| type HandleE func(http.ResponseWriter, *http.Request, httprouter.Params) error |
| |
| func ErrAwareHandle(h HandleE) httprouter.Handle { |
| return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
| err := h(w, r, ps) |
| if err != nil { |
| switch e := err.(type) { |
| case *ApiError: |
| |
| |
| |
| |
| |
| |
| |
| |
| http.Error(w, e.Error(), e.StatusCode()) |
| default: |
| http.Error(w, e.Error(), 500) |
| } |
| |
| messageChain := ExtractErrorMessageChain(err) |
| fmt.Fprintf(os.Stderr, "error:\n%s\n", messageChain) |
| } |
| } |
| } |
| |
| |
| |
| type Input struct { |
| Msg string `json:msg` |
| } |
| |
| func example(w http.ResponseWriter, r *http.Request, ps httprouter.Params) error { |
| name := ps.ByName("name") |
| if name != "say" { |
| return NewApiError(404, "not found", errors.New("must be /error_test/say")) |
| } |
| |
| body, err := ioutil.ReadAll(r.Body) |
| if err != nil { |
| return fmt.Errorf("body read failed: %v", err) |
| } |
| |
| if len(body) < 2 { |
| return errors.New("body is not enough") |
| } |
| |
| var input Input |
| if err = json.Unmarshal(body, &input); err != nil { |
| return NewApiError(400, "bad request", err) |
| } |
| |
| if input.Msg == "" { |
| return NewApiError(400, "msg lost", nil) |
| } |
| |
| if input.Msg == "FQ" { |
| return NewApiError(403, "you dirty", fmt.Errorf("変態 %w", errors.New("助兵衛"))) |
| } |
| |
| w.WriteHeader(200) |
| w.Write([]byte("OK\n")) |
| return nil |
| } |
| |
| func main() { |
| router := httprouter.New() |
| router.POST("/error_test/:name", ErrAwareHandle(example)) |
| |
| err := http.ListenAndServe(":8000", router) |
| if err != nil { |
| panic(err) |
| } |
| } |
| |
| |
| |