From a5481765f097d6da9edbc4009d6ce731f6bee726 Mon Sep 17 00:00:00 2001 From: ElvinChan Date: Fri, 13 Mar 2020 16:50:56 +0800 Subject: [PATCH] #29 Refactory error dispatch way between packages --- controllers/discount_api.go | 41 ++++++++++----------- controllers/utils.go | 64 ++++++++++----------------------- factory/errors.go | 71 +++++++++++++++++++++++++++++++++++++ models/discount.go | 41 ++++++++++++++++----- 4 files changed, 142 insertions(+), 75 deletions(-) create mode 100644 factory/errors.go diff --git a/controllers/discount_api.go b/controllers/discount_api.go index 06372a1..010d50f 100644 --- a/controllers/discount_api.go +++ b/controllers/discount_api.go @@ -20,13 +20,14 @@ type DiscountApiController struct { func (c DiscountApiController) Init(g echoswagger.ApiGroup) { g.GET("", c.GetAll).AddParamQueryNested(SearchInput{}) g.POST("", c.Create).AddParamBody(DiscountInput{}, "body", "", true) - g.GET("/:id", c.GetOne).AddParamPath("", "id", "") - g.PUT("/:id", c.Update).AddParamBody(DiscountInput{}, "body", "", true) + g.GET("/:id", c.GetOne).AddParamPath(0, "id", "") + g.PUT("/:id", c.Update).AddParamPath(0, "id", "").AddParamBody(DiscountInput{}, "body", "", true) } + func (DiscountApiController) GetAll(c echo.Context) error { var v SearchInput if err := c.Bind(&v); err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } if v.MaxResultCount == 0 { v.MaxResultCount = DefaultMaxResultCount @@ -45,7 +46,7 @@ func (DiscountApiController) GetAll(c echo.Context) error { totalCount, items, err := models.Discount{}.GetAll(c.Request().Context(), v.Sortby, v.Order, v.SkipCount, v.MaxResultCount) if err != nil { - return ReturnApiFail(c, http.StatusInternalServerError, ApiErrorDB, err) + return renderFail(c, err) } // behavior log @@ -57,7 +58,7 @@ func (DiscountApiController) GetAll(c echo.Context) error { }). Log("SearchComplete") - return ReturnApiSucc(c, http.StatusOK, ArrayResult{ + return renderSucc(c, http.StatusOK, ArrayResult{ TotalCount: totalCount, Items: items, }) @@ -66,56 +67,56 @@ func (DiscountApiController) GetAll(c echo.Context) error { func (DiscountApiController) Create(c echo.Context) error { var v DiscountInput if err := c.Bind(&v); err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } if err := c.Validate(&v); err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } discount, err := v.ToModel() if err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } if _, err := discount.Create(c.Request().Context()); err != nil { - return ReturnApiFail(c, http.StatusInternalServerError, ApiErrorDB, err) + return renderFail(c, err) } - return ReturnApiSucc(c, http.StatusOK, discount) + return renderSucc(c, http.StatusOK, discount) } func (DiscountApiController) GetOne(c echo.Context) error { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } v, err := models.Discount{}.GetById(c.Request().Context(), id) if err != nil { - return ReturnApiFail(c, http.StatusInternalServerError, ApiErrorDB, err) + return renderFail(c, err) } if v == nil { - return ReturnApiFail(c, http.StatusNotFound, ApiErrorNotFound, nil) + return renderFail(c, factory.ErrorNotFound.New(nil)) } - return ReturnApiSucc(c, http.StatusOK, v) + return renderSucc(c, http.StatusOK, v) } func (DiscountApiController) Update(c echo.Context) error { var v DiscountInput if err := c.Bind(&v); err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } if err := c.Validate(&v); err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } discount, err := v.ToModel() if err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { - return ReturnApiFail(c, http.StatusBadRequest, ApiErrorParameter, err) + return renderFail(c, factory.ErrorParameter.New(err)) } discount.Id = id if err := discount.Update(c.Request().Context()); err != nil { - return ReturnApiFail(c, http.StatusInternalServerError, ApiErrorDB, err) + return renderFail(c, err) } - return ReturnApiSucc(c, http.StatusOK, discount) + return renderSucc(c, http.StatusOK, v) } diff --git a/controllers/utils.go b/controllers/utils.go index 9708cad..0b17df5 100644 --- a/controllers/utils.go +++ b/controllers/utils.go @@ -1,7 +1,7 @@ package controllers import ( - "fmt" + "errors" "net/http" "net/url" "strings" @@ -18,15 +18,9 @@ const ( ) type ApiResult struct { - Result interface{} `json:"result"` - Success bool `json:"success"` - Error ApiError `json:"error"` -} - -type ApiError struct { - Code int `json:"code,omitempty"` - Message string `json:"message,omitempty"` - Details interface{} `json:"details,omitempty"` + Result interface{} `json:"result"` + Success bool `json:"success"` + Error factory.Error `json:"error"` } type ArrayResult struct { @@ -34,50 +28,28 @@ type ArrayResult struct { TotalCount int64 `json:"totalCount"` } -var ( - // System Error - ApiErrorSystem = ApiError{Code: 10001, Message: "System Error"} - ApiErrorServiceUnavailable = ApiError{Code: 10002, Message: "Service unavailable"} - ApiErrorRemoteService = ApiError{Code: 10003, Message: "Remote service error"} - ApiErrorIPLimit = ApiError{Code: 10004, Message: "IP limit"} - ApiErrorPermissionDenied = ApiError{Code: 10005, Message: "Permission denied"} - ApiErrorIllegalRequest = ApiError{Code: 10006, Message: "Illegal request"} - ApiErrorHTTPMethod = ApiError{Code: 10007, Message: "HTTP method is not suported for this request"} - ApiErrorParameter = ApiError{Code: 10008, Message: "Parameter error"} - ApiErrorMissParameter = ApiError{Code: 10009, Message: "Miss required parameter"} - ApiErrorDB = ApiError{Code: 10010, Message: "DB error, please contact the administator"} - ApiErrorTokenInvaild = ApiError{Code: 10011, Message: "Token invaild"} - ApiErrorMissToken = ApiError{Code: 10012, Message: "Miss token"} - ApiErrorVersion = ApiError{Code: 10013, Message: "API version %s invalid"} - ApiErrorNotFound = ApiError{Code: 10014, Message: "Resource not found"} - // Business Error - ApiErrorUserNotExists = ApiError{Code: 20001, Message: "User does not exists"} - ApiErrorPassword = ApiError{Code: 20002, Message: "Password error"} -) - -func ReturnApiFail(c echo.Context, status int, apiError ApiError, err error, v ...interface{}) error { - str := "" - if err != nil { - str = err.Error() - behaviorlog.FromCtx(c.Request().Context()).WithError(err) +func renderFail(c echo.Context, err error) error { + if err == nil { + err = factory.ErrorSystem.New(nil) } - return c.JSON(status, ApiResult{ - Success: false, - Error: ApiError{ - Code: apiError.Code, - Message: fmt.Sprintf(apiError.Message, v...), - Details: str, - }, - }) + behaviorlog.FromCtx(c.Request().Context()).WithError(err) + var apiError *factory.Error + if ok := errors.As(err, &apiError); ok { + return c.JSON(apiError.Status(), ApiResult{ + Success: false, + Error: *apiError, + }) + } + return err } -func ReturnApiSucc(c echo.Context, status int, result interface{}) error { +func renderSucc(c echo.Context, status int, result interface{}) error { req := c.Request() if req.Method == "POST" || req.Method == "PUT" || req.Method == "DELETE" { if session, ok := factory.DB(req.Context()).(*xorm.Session); ok { err := session.Commit() if err != nil { - return ReturnApiFail(c, http.StatusInternalServerError, ApiErrorDB, err) + return renderFail(c, factory.ErrorDB.New(err)) } } } diff --git a/factory/errors.go b/factory/errors.go new file mode 100644 index 0000000..804d62f --- /dev/null +++ b/factory/errors.go @@ -0,0 +1,71 @@ +package factory + +import ( + "fmt" + "net/http" +) + +const WrapErrorMessage = "echosample error" + +type Error struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Details string `json:"details,omitempty"` + err error + status int +} + +func (t ErrorTemplate) New(err error, v ...interface{}) *Error { + e := Error{ + Code: t.Code, + Message: fmt.Sprintf(t.Message, v...), + err: err, + } + if err != nil { + e.Details = fmt.Sprintf("%s: %s", WrapErrorMessage, err.Error()) + } + return &e +} + +func (e *Error) Error() string { + if e == nil { + return "" + } + return e.Details +} + +func (e *Error) Unwrap() error { + if e == nil { + return nil + } + return e.err +} + +func (e *Error) Status() int { + if e == nil || e.status == 0 { + return http.StatusInternalServerError + } + return e.status +} + +type ErrorTemplate Error + +var ( + // System Error + ErrorSystem = ErrorTemplate{Code: 10001, Message: "System Error"} + ErrorServiceUnavailable = ErrorTemplate{Code: 10002, Message: "Service unavailable"} + ErrorRemoteService = ErrorTemplate{Code: 10003, Message: "Remote service error"} + ErrorIPLimit = ErrorTemplate{Code: 10004, Message: "IP limit"} + ErrorPermissionDenied = ErrorTemplate{Code: 10005, Message: "Permission denied", status: http.StatusForbidden} + ErrorIllegalRequest = ErrorTemplate{Code: 10006, Message: "Illegal request", status: http.StatusBadRequest} + ErrorHTTPMethod = ErrorTemplate{Code: 10007, Message: "HTTP method is not suported for this request", status: http.StatusMethodNotAllowed} + ErrorParameter = ErrorTemplate{Code: 10008, Message: "Parameter error", status: http.StatusBadRequest} + ErrorMissParameter = ErrorTemplate{Code: 10009, Message: "Miss required parameter", status: http.StatusBadRequest} + ErrorDB = ErrorTemplate{Code: 10010, Message: "DB error, please contact the administator"} + ErrorTokenInvaild = ErrorTemplate{Code: 10011, Message: "Token invaild", status: http.StatusUnauthorized} + ErrorMissToken = ErrorTemplate{Code: 10012, Message: "Miss token", status: http.StatusUnauthorized} + ErrorVersion = ErrorTemplate{Code: 10013, Message: "API version %s invalid"} + ErrorNotFound = ErrorTemplate{Code: 10014, Message: "Resource not found", status: http.StatusNotFound} + // Business Error + ErrorDiscountNotExists = ErrorTemplate{Code: 20001, Message: "Discount %d does not exists"} +) diff --git a/models/discount.go b/models/discount.go index 333ea69..3e49862 100644 --- a/models/discount.go +++ b/models/discount.go @@ -21,17 +21,23 @@ type Discount struct { } func (d *Discount) Create(ctx context.Context) (int64, error) { - return factory.DB(ctx).Insert(d) + affected, err := factory.DB(ctx).Insert(d) + if err != nil { + return 0, factory.ErrorDB.New(err) + } + return affected, nil } + func (Discount) GetById(ctx context.Context, id int64) (*Discount, error) { var v Discount if has, err := factory.DB(ctx).ID(id).Get(&v); err != nil { - return nil, err + return nil, factory.ErrorDB.New(err) } else if !has { return nil, nil } return &v, nil } + func (Discount) GetAll(ctx context.Context, sortby, order []string, offset, limit int) (int64, []Discount, error) { q := factory.DB(ctx) if err := setSortOrder(q, sortby, order); err != nil { @@ -41,16 +47,33 @@ func (Discount) GetAll(ctx context.Context, sortby, order []string, offset, limi var items []Discount totalCount, err := q.Limit(limit, offset).FindAndCount(&items) if err != nil { - return 0, nil, err + return 0, nil, factory.ErrorDB.New(err) } return totalCount, items, nil } -func (d *Discount) Update(ctx context.Context) (err error) { - _, err = factory.DB(ctx).ID(d.Id).Update(d) - return + +func (d *Discount) Update(ctx context.Context) error { + if origin, err := d.GetById(ctx, d.Id); err != nil { + return factory.ErrorDB.New(err) + } else if origin == nil { + return factory.ErrorDiscountNotExists.New(err, d.Id) + } + + if _, err := factory.DB(ctx).ID(d.Id).Update(d); err != nil { + return factory.ErrorDB.New(err) + } + return nil } -func (Discount) Delete(ctx context.Context, id int64) (err error) { - _, err = factory.DB(ctx).ID(id).Delete(&Discount{}) - return +func (Discount) Delete(ctx context.Context, id int64) error { + if origin, err := (Discount{}).GetById(ctx, id); err != nil { + return factory.ErrorDB.New(err) + } else if origin == nil { + return factory.ErrorDiscountNotExists.New(err, id) + } + + if _, err := factory.DB(ctx).ID(id).Delete(&Discount{}); err != nil { + return factory.ErrorDB.New(err) + } + return nil }