thread-master/apps/backend/internal/response/response.go

106 lines
3.5 KiB
Go

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 }