You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
4.8 KiB
246 lines
4.8 KiB
package crud
|
|
|
|
import (
|
|
"errors"
|
|
"github.com/labstack/echo/v4"
|
|
"net/http"
|
|
"reflect"
|
|
"sorbet/pkg/db"
|
|
"sorbet/pkg/rsp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type (
|
|
// RequestGuarder 参数守卫函数签名
|
|
RequestGuarder func(c echo.Context, req any) error
|
|
|
|
// BodyBinder 将请求体绑定到结构体上
|
|
BodyBinder interface {
|
|
BindBody(c echo.Context, i any) (err error)
|
|
}
|
|
)
|
|
|
|
// 使用内置数据绑定函数
|
|
// 默认使用 echo.DefaultBinder
|
|
var binder BodyBinder
|
|
|
|
// Validate 验证数据
|
|
func Validate(c echo.Context, t any, guards ...RequestGuarder) error {
|
|
err := c.Validate(t)
|
|
if err != nil && !errors.Is(err, echo.ErrValidatorNotRegistered) {
|
|
return err
|
|
}
|
|
if v, ok := t.(interface{ Validate() error }); ok {
|
|
err = v.Validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, guard := range guards {
|
|
err = guard(c, t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Bind 将提交的参数绑定到泛型 T 的实例上
|
|
func Bind[T any](c echo.Context, guards ...RequestGuarder) (*T, error) {
|
|
var req T
|
|
if err := c.Bind(&req); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := Validate(c, &req, guards...); err != nil {
|
|
return nil, err
|
|
}
|
|
return &req, nil
|
|
}
|
|
|
|
// BindBody 将提交请求体绑定到泛型 T 的实例上
|
|
func BindBody[T any](c echo.Context, guards ...RequestGuarder) (*T, error) {
|
|
b, ok := c.Echo().Binder.(BodyBinder)
|
|
if !ok || b == nil {
|
|
if binder == nil {
|
|
binder = &echo.DefaultBinder{}
|
|
}
|
|
b = binder
|
|
}
|
|
var req T
|
|
if err := b.BindBody(c, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := Validate(c, &req, guards...); err != nil {
|
|
return nil, err
|
|
}
|
|
return &req, nil
|
|
}
|
|
|
|
type identifier struct {
|
|
ID any `json:"id" xml:"id"`
|
|
}
|
|
|
|
// BindID 自请求中获取需要的数据编号
|
|
// 查找的是名称为 `id` 的数据,查找顺序如下
|
|
// 1. 查看路由中是否定义,比如:`/configs/:id`
|
|
// 2. 查看查询字符串中是否存在,比如:`?id=123`
|
|
// 3. 查看请求体中是否存在,支持 json 和 xml 两种格式。
|
|
func BindID(c echo.Context) (id any, err error) {
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
c.Logger().Debugf("%#v", recovered)
|
|
id = nil
|
|
err = rsp.ErrBadParams
|
|
}
|
|
}()
|
|
for i, name := range c.ParamNames() {
|
|
if name == "id" {
|
|
id = c.ParamValues()[i]
|
|
if id != "" {
|
|
return
|
|
}
|
|
break
|
|
}
|
|
}
|
|
switch c.Request().Method {
|
|
case http.MethodGet, http.MethodDelete, http.MethodHead:
|
|
for key, values := range c.QueryParams() {
|
|
if key == "id" {
|
|
id = values[0]
|
|
if id != "" {
|
|
return
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if c.Request().ContentLength == 0 {
|
|
err = rsp.ErrBadParams
|
|
return
|
|
}
|
|
var data *identifier
|
|
data, err = BindBody[identifier](c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if data.ID == nil {
|
|
return nil, rsp.ErrBadParams
|
|
}
|
|
rv := reflect.ValueOf(data.ID)
|
|
if rv.IsZero() || rv.IsNil() {
|
|
return nil, rsp.ErrBadParams
|
|
}
|
|
return data.ID, nil
|
|
}
|
|
|
|
func BindQuery[T any](c echo.Context, qb *db.QueryBuilder[T]) (page, limit int, err error) {
|
|
query := c.QueryParams()
|
|
var paginating bool
|
|
for key, values := range query {
|
|
switch key {
|
|
case "sortby":
|
|
for _, s := range values {
|
|
if s[0] == '+' {
|
|
qb.AscentBy(s[1:])
|
|
} else if s[0] == '-' {
|
|
qb.DescentBy(s[1:])
|
|
} else {
|
|
qb.AscentBy(s)
|
|
}
|
|
}
|
|
case "limit", "page":
|
|
var v int
|
|
if values[0] != "" {
|
|
v, err = strconv.Atoi(values[0])
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
if v <= 0 {
|
|
err = rsp.ErrInternal
|
|
return
|
|
}
|
|
if key == "limit" {
|
|
qb.Limit(v)
|
|
limit = v
|
|
} else {
|
|
paginating = true
|
|
page = max(v, 1)
|
|
}
|
|
default:
|
|
v := values[0]
|
|
i := strings.IndexByte(key, '#')
|
|
if i == -1 {
|
|
qb.Eq(key, v)
|
|
continue
|
|
}
|
|
switch k, op := key[:i], key[i+1:]; op {
|
|
case "=":
|
|
qb.Eq(k, v)
|
|
case "!=":
|
|
qb.Neq(k, v)
|
|
case "<":
|
|
qb.Lt(k, v)
|
|
case "<=":
|
|
qb.Lte(k, v)
|
|
case ">":
|
|
qb.Gt(k, v)
|
|
case ">=":
|
|
qb.Gte(k, v)
|
|
case "<>", "><":
|
|
var less, more any
|
|
switch len(values) {
|
|
case 2:
|
|
less, more = values[0], values[1]
|
|
case 1:
|
|
vs := strings.Split(v, ",")
|
|
if len(vs) != 2 || vs[0] == "" || vs[1] == "" {
|
|
err = rsp.ErrBadParams
|
|
return
|
|
}
|
|
less, more = vs[0], vs[1]
|
|
default:
|
|
err = rsp.ErrBadParams
|
|
return
|
|
}
|
|
if op == "<>" {
|
|
qb.Between(k, less, more)
|
|
} else {
|
|
qb.NotBetween(k, key, more)
|
|
}
|
|
case "nil":
|
|
qb.IsNull(k)
|
|
case "!nil":
|
|
qb.NotNull(k)
|
|
case "~":
|
|
qb.Like(k, v)
|
|
case "!~":
|
|
qb.NotLike(k, v)
|
|
case "in", "!in":
|
|
if len(values) == 1 {
|
|
values = strings.Split(v, ",")
|
|
}
|
|
vs := make([]any, len(values))
|
|
for i, value := range values {
|
|
vs[i] = value
|
|
}
|
|
if op == "in" {
|
|
qb.In(k, vs...)
|
|
} else {
|
|
qb.NotIn(k, vs...)
|
|
}
|
|
default:
|
|
qb.Eq(key, v)
|
|
}
|
|
}
|
|
}
|
|
if paginating {
|
|
if limit == 0 {
|
|
limit = 30
|
|
qb.Limit(limit)
|
|
}
|
|
qb.Offset((page - 1) * limit)
|
|
}
|
|
return
|
|
}
|
|
|