package response import ( "context" "errors" "net/http" "apps/backend/internal/domain" memberDomain "apps/backend/internal/module/member/domain" tokenDomain "apps/backend/internal/module/token/domain" "github.com/zeromicro/go-zero/rest/httpx" ) // Envelope is the standard JSON body (AGENTS.md). type Envelope struct { Code int64 `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` Error interface{} `json:"error"` } // Write is used by goctl-generated handlers. func Write(ctx context.Context, w http.ResponseWriter, data interface{}, err error) { if err != nil { status, env := mapError(err) httpx.WriteJsonCtx(ctx, w, status, env) return } httpx.WriteJsonCtx(ctx, w, http.StatusOK, Envelope{ Code: domain.SuccessCode, Message: domain.SuccessMessage, Data: data, Error: nil, }) } func WrapRequestError(err error) error { if err == nil { return nil } return &bizError{http: http.StatusBadRequest, code: 400001, msg: err.Error()} } type bizError struct { http int code int64 msg string } func (e *bizError) Error() string { return e.msg } func Biz(httpStatus int, code int64, msg string) error { return &bizError{http: httpStatus, code: code, msg: msg} } func mapError(err error) (int, Envelope) { var be *bizError if errors.As(err, &be) { return be.http, Envelope{Code: be.code, Message: be.msg} } switch { case errors.Is(err, memberDomain.ErrBadPassword): return http.StatusUnauthorized, Envelope{Code: 401010, Message: "invalid email or password"} case errors.Is(err, memberDomain.ErrSuspended): return http.StatusForbidden, Envelope{Code: 403001, Message: "account suspended"} case errors.Is(err, memberDomain.ErrEmailTaken): return http.StatusConflict, Envelope{Code: 409001, Message: "email already registered"} case errors.Is(err, memberDomain.ErrWeakPassword): // 與 domain.PasswordPolicyEN、FE api.err.400003 / password.policy.hint 同一句 return http.StatusBadRequest, Envelope{Code: 400003, Message: memberDomain.PasswordPolicyEN} case errors.Is(err, memberDomain.ErrInvalidCode): return http.StatusBadRequest, Envelope{Code: 400004, Message: "invalid or expired code"} case errors.Is(err, memberDomain.ErrEmailNotRegistered): return http.StatusNotFound, Envelope{Code: 404002, Message: "email not registered"} case errors.Is(err, memberDomain.ErrNotFound): return http.StatusNotFound, Envelope{Code: 404001, Message: "not found"} case errors.Is(err, memberDomain.ErrLastAdmin): return http.StatusBadRequest, Envelope{Code: 400021, Message: err.Error()} case errors.Is(err, memberDomain.ErrSelfSuspend): return http.StatusBadRequest, Envelope{Code: 400020, Message: err.Error()} case errors.Is(err, tokenDomain.ErrInvalidToken): return http.StatusUnauthorized, Envelope{Code: 401002, Message: "invalid token"} default: return http.StatusInternalServerError, Envelope{Code: 500000, Message: err.Error()} } } func OK(w http.ResponseWriter, data interface{}) { Write(context.Background(), w, data, nil) } func Fail(w http.ResponseWriter, httpStatus int, code int64, message string, errDetail interface{}) { if code == domain.SuccessCode { code = 400000 } httpx.WriteJson(w, httpStatus, Envelope{Code: code, Message: message, Data: nil, Error: errDetail}) } func NotImplemented(w http.ResponseWriter, feature string) { Fail(w, http.StatusNotImplemented, 501000, "not implemented: "+feature, map[string]string{"feature": feature}) } func IsSuccessCode(code int64) bool { return code == domain.SuccessCode }