package rsp import ( "bytes" "encoding/json" "errors" "github.com/labstack/echo/v4" "net/http" ) var ( TextMarshaller func(map[string]any) (string, error) HtmlMarshaller func(map[string]any) (string, error) JsonpCallbacks []string DefaultJsonpCallback string ) func init() { TextMarshaller = toText HtmlMarshaller = toText JsonpCallbacks = []string{"callback", "cb", "jsonp"} DefaultJsonpCallback = "callback" } func toText(m map[string]any) (string, error) { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetEscapeHTML(true) if err := enc.Encode(m); err != nil { return "", err } else { return buf.String(), nil } } type RespondValuer interface { RespondValue() any } type RespondData[T any] struct { data T } func (r *RespondData[T]) RespondValue() any { return r.data } type response struct { code int headers map[string]string cookies []*http.Cookie err error message string data any } type Option func(o *response) func StatusCode(code int) Option { return func(o *response) { o.code = code } } func Header(key, value string) Option { return func(o *response) { if o.headers == nil { o.headers = make(map[string]string) } o.headers[key] = value } } func Cookie(cookie *http.Cookie) Option { return func(o *response) { if o.cookies != nil { for i, h := range o.cookies { if h.Name == cookie.Name { o.cookies[i] = cookie return } } } o.cookies = append(o.cookies, cookie) } } func Message(msg string) Option { return func(o *response) { o.message = msg } } func Data(data any) Option { return func(o *response) { o.data = data } } func respond(c echo.Context, o *response) error { defer func() { if o.err != nil { c.Logger().Error(o.err) } }() m := map[string]any{ "code": nil, "success": false, "message": o.message, } var err *Error if errors.As(o.err, &err) { m["code"] = err.code m["message"] = err.text m["success"] = errors.Is(err, ErrOK) } else if o.err != nil { m["code"] = ErrInternal.code m["message"] = o.err.Error() } else { m["code"] = 0 m["success"] = true if o.data != nil { if v, ok := o.data.(RespondValuer); ok { m["data"] = v.RespondValue() } else { m["data"] = o.data } } } if m["message"] == "" { m["message"] = http.StatusText(o.code) } if c.Response().Committed { return nil } if o.headers != nil { header := c.Response().Header() for key, value := range o.headers { header.Set(key, value) } } if o.cookies != nil { for _, cookie := range o.cookies { c.SetCookie(cookie) } } r := c.Request() if r.Method == http.MethodHead { return c.NoContent(o.code) } slice := parse(r.Header.Get("Accept")) for _, a := range slice { switch x := a.Type + "/" + a.Subtype; x { case echo.MIMEApplicationJavaScript: qs := c.Request().URL.Query() for _, name := range JsonpCallbacks { if cb := qs.Get(name); cb != "" { return c.JSONP(o.code, cb, m) } } return c.JSONP(o.code, DefaultJsonpCallback, m) case echo.MIMEApplicationJSON: return c.JSON(o.code, m) case echo.MIMEApplicationXML, echo.MIMETextXML: return c.XML(o.code, m) case echo.MIMETextHTML: if html, err := HtmlMarshaller(m); err != nil { return err } else { return c.HTML(o.code, html) } case echo.MIMETextPlain: if text, err := TextMarshaller(m); err != nil { return err } else { return c.String(o.code, text) } } } return c.JSON(o.code, m) } func Respond(c echo.Context, opts ...Option) error { o := response{code: http.StatusOK} for _, option := range opts { option(&o) } return respond(c, &o) } func Ok(c echo.Context, data any) error { return Respond(c, Data(data)) } func Created(c echo.Context, data any) error { return Respond(c, Data(data), StatusCode(http.StatusCreated)) } // Fail 响应一个错误 func Fail(c echo.Context, err error, opts ...Option) error { o := response{code: http.StatusInternalServerError} for _, option := range opts { option(&o) } o.err = err var he *echo.HTTPError if errors.As(err, &he) { o.code = he.Code } return respond(c, &o) } // InternalError 响应一个服务器内部错误 func InternalError(c echo.Context, message ...string) error { return Fail(c, ErrInternal.WithText(message...)) } // ServiceUnavailable 响应一个服务暂不可用的错误 func ServiceUnavailable(c echo.Context, message ...string) error { return Fail(c, ErrServiceUnavailable.WithText(message...), StatusCode(http.StatusServiceUnavailable)) } // Unauthorized 需要一个身份验证凭据异常的错误 func Unauthorized(c echo.Context, message ...string) error { return Fail(c, ErrUnauthorized.WithText(message...), StatusCode(http.StatusUnauthorized)) } // Forbidden 响应一个不具有访问资源所需权限的错误(用户通过了身份验证) func Forbidden(c echo.Context, message ...string) error { return Fail(c, ErrForbidden.WithText(message...), StatusCode(http.StatusForbidden)) } // UnprocessableEntity 响应一个处理客户端上传失败的错误 func UnprocessableEntity(c echo.Context, message ...string) error { return Fail(c, ErrUnprocessableEntity.WithText(message...), StatusCode(http.StatusUnprocessableEntity)) } // BadRequest 响应一个服务器不理解客户端请求的错误 func BadRequest(c echo.Context, message ...string) error { return Fail(c, ErrBadRequest.WithText(message...), StatusCode(http.StatusBadRequest)) } // BadParams 响应一个客户端提交的参数不符合要求的错误 func BadParams(c echo.Context, message ...string) error { return Fail(c, ErrBadParams.WithText(message...), StatusCode(http.StatusBadRequest)) } // RecordNotFound 响应一个数据不存在的错误 func RecordNotFound(c echo.Context, message ...string) error { return Fail(c, ErrRecordNotFound.WithText(message...), StatusCode(http.StatusNotFound)) }