chore: import clean Agent Desk snapshot (2/7)
This commit is contained in:
46
internal/pkg/httpx/context.go
Normal file
46
internal/pkg/httpx/context.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/httpx/params"
|
||||
"agent-desk/internal/pkg/openidentity"
|
||||
"agent-desk/internal/pkg/tracex"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/common/strs"
|
||||
)
|
||||
|
||||
const (
|
||||
ctxKeyExternalUser = "externalUser"
|
||||
)
|
||||
|
||||
func SetExternalUser(ctx *gin.Context, ext *openidentity.ExternalUser) {
|
||||
ctx.Set(ctxKeyExternalUser, ext)
|
||||
}
|
||||
|
||||
func GetExternalUser(ctx *gin.Context) *openidentity.ExternalUser {
|
||||
v, _ := ctx.Get(ctxKeyExternalUser)
|
||||
ext, _ := v.(*openidentity.ExternalUser)
|
||||
return ext
|
||||
}
|
||||
|
||||
func GetChannelID(ctx *gin.Context) string {
|
||||
if channelID := ctx.GetHeader("X-Channel-ID"); strs.IsNotBlank(channelID) {
|
||||
return channelID
|
||||
}
|
||||
if channelID, _ := params.Get(ctx, "channelId"); strs.IsNotBlank(channelID) {
|
||||
return channelID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetRequestID(ctx *gin.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if value, ok := ctx.Get(tracex.GinRequestIDKey); ok {
|
||||
if requestID, ok := value.(string); ok {
|
||||
return tracex.NormalizeRequestID(requestID)
|
||||
}
|
||||
}
|
||||
return tracex.NormalizeRequestID(ctx.GetHeader(tracex.RequestIDHeader))
|
||||
}
|
||||
12
internal/pkg/httpx/error.go
Normal file
12
internal/pkg/httpx/error.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func JsonErrorMsg(ctx *gin.Context, key string, args ...any) *web.JsonResult {
|
||||
return web.JsonErrorMsg(i18nx.T(ctx, key, args...))
|
||||
}
|
||||
289
internal/pkg/httpx/params/params.go
Normal file
289
internal/pkg/httpx/params/params.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/mlogclub/simple/common/dates"
|
||||
"github.com/mlogclub/simple/common/jsons"
|
||||
"github.com/mlogclub/simple/common/strs"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
var (
|
||||
decoder = schema.NewDecoder()
|
||||
validate = validator.New()
|
||||
)
|
||||
|
||||
func init() {
|
||||
decoder.SetAliasTag("form")
|
||||
decoder.ZeroEmpty(true)
|
||||
decoder.IgnoreUnknownKeys(true)
|
||||
}
|
||||
|
||||
func paramError(name string) error {
|
||||
return fmt.Errorf("unable to find param value '%s'", name)
|
||||
}
|
||||
|
||||
func ReadForm(ctx *gin.Context, obj any) error {
|
||||
if ctx == nil {
|
||||
return errors.New("request context is nil")
|
||||
}
|
||||
if err := ctx.Request.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
values := ctx.Request.Form
|
||||
if len(values) == 0 {
|
||||
if err := ctx.Request.ParseMultipartForm(32 << 20); err != nil && !errors.Is(err, http.ErrNotMultipart) {
|
||||
return err
|
||||
}
|
||||
values = ctx.Request.Form
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := decoder.Decode(obj, values); err != nil {
|
||||
return err
|
||||
}
|
||||
return validateStruct(obj)
|
||||
}
|
||||
|
||||
func ReadJSON(ctx *gin.Context, obj any) error {
|
||||
if ctx == nil {
|
||||
return errors.New("request context is nil")
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return validateStruct(obj)
|
||||
}
|
||||
|
||||
func validateStruct(obj any) error {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
value := reflect.ValueOf(obj)
|
||||
for value.Kind() == reflect.Pointer {
|
||||
if value.IsNil() {
|
||||
return validate.Struct(obj)
|
||||
}
|
||||
value = value.Elem()
|
||||
}
|
||||
if value.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
return validate.Struct(obj)
|
||||
}
|
||||
|
||||
func Get(ctx *gin.Context, name string) (string, bool) {
|
||||
str := FormValue(ctx, name)
|
||||
return str, str != ""
|
||||
}
|
||||
|
||||
func GetInt64(ctx *gin.Context, name string) (int64, bool) {
|
||||
str, ok := Get(ctx, name)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
value, err := cast.ToInt64E(str)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func GetInt(ctx *gin.Context, name string) (int, bool) {
|
||||
str, ok := Get(ctx, name)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
value, err := cast.ToIntE(str)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func GetBool(ctx *gin.Context, name string) (bool, bool) {
|
||||
str, ok := Get(ctx, name)
|
||||
if !ok {
|
||||
return false, false
|
||||
}
|
||||
value, err := cast.ToBoolE(str)
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func GetTime(ctx *gin.Context, name string) *time.Time {
|
||||
value, _ := Get(ctx, name)
|
||||
if strs.IsBlank(value) {
|
||||
return nil
|
||||
}
|
||||
layouts := []string{dates.FmtDateTime, dates.FmtDate, dates.FmtDateTimeNoSeconds}
|
||||
for _, layout := range layouts {
|
||||
if ret, err := dates.Parse(value, layout); err == nil {
|
||||
return &ret
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetInt64Arr(ctx *gin.Context, name string) []int64 {
|
||||
str, ok := Get(ctx, name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
str = strings.TrimSpace(str)
|
||||
if strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]") {
|
||||
var ret []int64
|
||||
if err := jsons.Parse(str, &ret); err != nil {
|
||||
slog.Error(err.Error())
|
||||
}
|
||||
return ret
|
||||
}
|
||||
return StrSplitToInt64Arr(str)
|
||||
}
|
||||
|
||||
func StrSplitToInt64Arr(str string) (ret []int64) {
|
||||
if strs.IsBlank(str) {
|
||||
return ret
|
||||
}
|
||||
for _, s := range strings.Split(str, ",") {
|
||||
i, err := cast.ToInt64E(strings.TrimSpace(s))
|
||||
if err == nil {
|
||||
ret = append(ret, i)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func FormValue(ctx *gin.Context, name string) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if value := ctx.PostForm(name); value != "" {
|
||||
return value
|
||||
}
|
||||
return ctx.Query(name)
|
||||
}
|
||||
|
||||
func FormValueRequired(ctx *gin.Context, name string) (string, error) {
|
||||
str := FormValue(ctx, name)
|
||||
if len(str) == 0 {
|
||||
return "", errorsx.InvalidParamI18n("error.param.required", name)
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func FormValueDefault(ctx *gin.Context, name, def string) string {
|
||||
if value := FormValue(ctx, name); value != "" {
|
||||
return value
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func FormValueInt(ctx *gin.Context, name string) (int, error) {
|
||||
str := FormValue(ctx, name)
|
||||
if str == "" {
|
||||
return 0, paramError(name)
|
||||
}
|
||||
return strconv.Atoi(str)
|
||||
}
|
||||
|
||||
func FormValueIntDefault(ctx *gin.Context, name string, def int) int {
|
||||
if v, err := FormValueInt(ctx, name); err == nil {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func FormValueInt64(ctx *gin.Context, name string) (int64, error) {
|
||||
str := FormValue(ctx, name)
|
||||
if str == "" {
|
||||
return 0, paramError(name)
|
||||
}
|
||||
return strconv.ParseInt(str, 10, 64)
|
||||
}
|
||||
|
||||
func FormValueInt64Default(ctx *gin.Context, name string, def int64) int64 {
|
||||
if v, err := FormValueInt64(ctx, name); err == nil {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func FormValueInt64Array(ctx *gin.Context, name string) []int64 {
|
||||
str := strings.TrimSpace(FormValue(ctx, name))
|
||||
if strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]") {
|
||||
var ret []int64
|
||||
if err := jsons.Parse(str, &ret); err != nil {
|
||||
slog.Error(err.Error())
|
||||
}
|
||||
return ret
|
||||
}
|
||||
return StrSplitToInt64Arr(str)
|
||||
}
|
||||
|
||||
func FormValueStringArray(ctx *gin.Context, name string) []string {
|
||||
str := FormValue(ctx, name)
|
||||
if len(str) == 0 {
|
||||
return nil
|
||||
}
|
||||
var ret []string
|
||||
for _, s := range strings.Split(str, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) > 0 {
|
||||
ret = append(ret, s)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func FormValueBool(ctx *gin.Context, name string) (bool, error) {
|
||||
str := FormValue(ctx, name)
|
||||
if str == "" {
|
||||
return false, paramError(name)
|
||||
}
|
||||
return strconv.ParseBool(str)
|
||||
}
|
||||
|
||||
func FormValueBoolDefault(ctx *gin.Context, name string, def bool) bool {
|
||||
str := FormValue(ctx, name)
|
||||
if str == "" {
|
||||
return def
|
||||
}
|
||||
value, err := strconv.ParseBool(str)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func FormDate(ctx *gin.Context, name string) *time.Time {
|
||||
return GetTime(ctx, name)
|
||||
}
|
||||
|
||||
func GetPaging(ctx *gin.Context) *sqls.Paging {
|
||||
page := FormValueIntDefault(ctx, "page", 1)
|
||||
limit := FormValueIntDefault(ctx, "limit", 20)
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
return &sqls.Paging{Page: page, Limit: limit}
|
||||
}
|
||||
31
internal/pkg/httpx/params/params_test.go
Normal file
31
internal/pkg/httpx/params/params_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func newJSONContext(body string) *gin.Context {
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(w)
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
ctx.Request.Header.Set("Content-Type", "application/json")
|
||||
return ctx
|
||||
}
|
||||
|
||||
func TestReadJSONAcceptsRootArray(t *testing.T) {
|
||||
var ids []int64
|
||||
|
||||
if err := ReadJSON(newJSONContext(`[3,4]`), &ids); err != nil {
|
||||
t.Fatalf("ReadJSON returned error: %v", err)
|
||||
}
|
||||
|
||||
if len(ids) != 2 || ids[0] != 3 || ids[1] != 4 {
|
||||
t.Fatalf("expected ids [3 4], got %#v", ids)
|
||||
}
|
||||
}
|
||||
79
internal/pkg/httpx/params/query_filters.go
Normal file
79
internal/pkg/httpx/params/query_filters.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/common/strs"
|
||||
"github.com/mlogclub/simple/common/strs/strcase"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
)
|
||||
|
||||
type QueryOp string
|
||||
|
||||
const (
|
||||
Eq QueryOp = "eq"
|
||||
Gt QueryOp = "gt"
|
||||
Lt QueryOp = "lt"
|
||||
Gte QueryOp = "gte"
|
||||
Lte QueryOp = "lte"
|
||||
Like QueryOp = "like"
|
||||
In QueryOp = "in"
|
||||
Starting QueryOp = "starting"
|
||||
Ending QueryOp = "ending"
|
||||
)
|
||||
|
||||
type QueryFilter struct {
|
||||
ParamName string
|
||||
Op QueryOp
|
||||
ColumnName string
|
||||
ValueWrapper func(origin string) string
|
||||
}
|
||||
|
||||
func NewPagedSqlCnd(ctx *gin.Context, filters ...QueryFilter) *sqls.Cnd {
|
||||
cnd := NewSqlCnd(ctx, filters...)
|
||||
p := GetPaging(ctx)
|
||||
cnd.Page(p.Page, p.Limit)
|
||||
return cnd
|
||||
}
|
||||
|
||||
func NewSqlCnd(ctx *gin.Context, filters ...QueryFilter) *sqls.Cnd {
|
||||
cnd := sqls.NewCnd()
|
||||
for _, filter := range filters {
|
||||
columnName := filter.ColumnName
|
||||
paramValue := FormValue(ctx, filter.ParamName)
|
||||
if strs.IsBlank(string(filter.Op)) {
|
||||
filter.Op = Eq
|
||||
}
|
||||
if filter.ValueWrapper != nil {
|
||||
paramValue = filter.ValueWrapper(paramValue)
|
||||
}
|
||||
if strs.IsBlank(paramValue) {
|
||||
continue
|
||||
}
|
||||
if strs.IsBlank(columnName) {
|
||||
columnName = strcase.ToSnake(filter.ParamName)
|
||||
}
|
||||
switch filter.Op {
|
||||
case Eq:
|
||||
cnd.Eq(columnName, paramValue)
|
||||
case Gt:
|
||||
cnd.Gt(columnName, paramValue)
|
||||
case Lt:
|
||||
cnd.Lt(columnName, paramValue)
|
||||
case Gte:
|
||||
cnd.Gte(columnName, paramValue)
|
||||
case Lte:
|
||||
cnd.Lte(columnName, paramValue)
|
||||
case Like:
|
||||
cnd.Like(columnName, paramValue)
|
||||
case Starting:
|
||||
cnd.Starting(columnName, paramValue)
|
||||
case Ending:
|
||||
cnd.Ending(columnName, paramValue)
|
||||
case In:
|
||||
cnd.In(columnName, strings.Split(paramValue, ","))
|
||||
}
|
||||
}
|
||||
return cnd
|
||||
}
|
||||
106
internal/pkg/httpx/params/query_params.go
Normal file
106
internal/pkg/httpx/params/query_params.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/common/strs/strcase"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
)
|
||||
|
||||
type QueryParams struct {
|
||||
Ctx *gin.Context
|
||||
sqls.Cnd
|
||||
}
|
||||
|
||||
func NewQueryParams(ctx *gin.Context) *QueryParams {
|
||||
return &QueryParams{Ctx: ctx}
|
||||
}
|
||||
|
||||
func (q *QueryParams) getValueByColumn(column string) string {
|
||||
if q.Ctx == nil {
|
||||
return ""
|
||||
}
|
||||
return FormValue(q.Ctx, strcase.ToLowerCamel(column))
|
||||
}
|
||||
|
||||
func (q *QueryParams) EqByReq(column string) *QueryParams {
|
||||
if value := q.getValueByColumn(column); len(value) > 0 {
|
||||
q.Eq(column, value)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) NotEqByReq(column string) *QueryParams {
|
||||
if value := q.getValueByColumn(column); len(value) > 0 {
|
||||
q.NotEq(column, value)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) GtByReq(column string) *QueryParams {
|
||||
if value := q.getValueByColumn(column); len(value) > 0 {
|
||||
q.Gt(column, value)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) GteByReq(column string) *QueryParams {
|
||||
if value := q.getValueByColumn(column); len(value) > 0 {
|
||||
q.Gte(column, value)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) LtByReq(column string) *QueryParams {
|
||||
if value := q.getValueByColumn(column); len(value) > 0 {
|
||||
q.Lt(column, value)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) LteByReq(column string) *QueryParams {
|
||||
if value := q.getValueByColumn(column); len(value) > 0 {
|
||||
q.Lte(column, value)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) LikeByReq(column string) *QueryParams {
|
||||
if value := q.getValueByColumn(column); len(value) > 0 {
|
||||
q.Like(column, value)
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) PageByReq() *QueryParams {
|
||||
if q.Ctx == nil {
|
||||
return q
|
||||
}
|
||||
paging := GetPaging(q.Ctx)
|
||||
q.Page(paging.Page, paging.Limit)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) Asc(column string) *QueryParams {
|
||||
q.Orders = append(q.Orders, sqls.OrderByCol{Column: column, Asc: true})
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) Desc(column string) *QueryParams {
|
||||
q.Orders = append(q.Orders, sqls.OrderByCol{Column: column, Asc: false})
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) Limit(limit int) *QueryParams {
|
||||
q.Page(1, limit)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *QueryParams) Page(page, limit int) *QueryParams {
|
||||
if q.Paging == nil {
|
||||
q.Paging = &sqls.Paging{Page: page, Limit: limit}
|
||||
} else {
|
||||
q.Paging.Page = page
|
||||
q.Paging.Limit = limit
|
||||
}
|
||||
return q
|
||||
}
|
||||
19
internal/pkg/httpx/path.go
Normal file
19
internal/pkg/httpx/path.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func GetPathInt64(ctx *gin.Context, name string) (int64, bool) {
|
||||
value, err := strconv.ParseInt(ctx.Param(name), 10, 64)
|
||||
if err != nil {
|
||||
WriteHttpStatusJSON(ctx, http.StatusBadRequest, web.JsonErrorMsg(i18nx.T(ctx, "error.path.invalid")))
|
||||
return 0, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
75
internal/pkg/httpx/response.go
Normal file
75
internal/pkg/httpx/response.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
type cursorData struct {
|
||||
results any
|
||||
cursor string
|
||||
hasMore bool
|
||||
}
|
||||
|
||||
type pageData struct {
|
||||
results any
|
||||
paging *sqls.Paging
|
||||
}
|
||||
|
||||
type localizedError interface {
|
||||
Message(locale string) string
|
||||
}
|
||||
|
||||
func CursorData(results any, cursor string, hasMore bool) any {
|
||||
return cursorData{results: results, cursor: cursor, hasMore: hasMore}
|
||||
}
|
||||
|
||||
func PageData(results any, paging *sqls.Paging) any {
|
||||
return pageData{results: results, paging: paging}
|
||||
}
|
||||
|
||||
func WriteJSON(ctx *gin.Context, result any) {
|
||||
ctx.JSON(http.StatusOK, buildJSONResult(ctx, result))
|
||||
}
|
||||
|
||||
func WriteHttpStatusJSON(ctx *gin.Context, statusCode int, result any) {
|
||||
ctx.JSON(statusCode, buildJSONResult(ctx, result))
|
||||
}
|
||||
|
||||
func buildJSONResult(ctx *gin.Context, result any) *web.JsonResult {
|
||||
switch value := result.(type) {
|
||||
case nil:
|
||||
return web.JsonSuccess()
|
||||
case *web.JsonResult:
|
||||
return value
|
||||
case web.JsonResult:
|
||||
return &value
|
||||
case *web.CodeError:
|
||||
return web.JsonError(value)
|
||||
case web.CodeError:
|
||||
return web.JsonError(&value)
|
||||
case *errorsx.I18nError:
|
||||
return value.JsonResult(i18nx.Locale(ctx))
|
||||
case errorsx.I18nError:
|
||||
return value.JsonResult(i18nx.Locale(ctx))
|
||||
case localizedError:
|
||||
return web.JsonErrorMsg(value.Message(i18nx.Locale(ctx)))
|
||||
case error:
|
||||
return web.JsonError(value)
|
||||
case cursorData:
|
||||
return web.JsonCursorData(value.results, value.cursor, value.hasMore)
|
||||
case pageData:
|
||||
return web.JsonPageData(value.results, value.paging)
|
||||
case web.RspBuilder:
|
||||
return value.JsonResult()
|
||||
case *web.RspBuilder:
|
||||
return value.JsonResult()
|
||||
default:
|
||||
return web.JsonData(result)
|
||||
}
|
||||
}
|
||||
104
internal/pkg/httpx/response_test.go
Normal file
104
internal/pkg/httpx/response_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/mlogclub/simple/web"
|
||||
)
|
||||
|
||||
func TestWriteJSONWrapsCommonResultTypes(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input any
|
||||
wantStatus int
|
||||
want web.JsonResult
|
||||
}{
|
||||
{
|
||||
name: "nil becomes success",
|
||||
input: nil,
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonSuccess(),
|
||||
},
|
||||
{
|
||||
name: "plain value becomes data",
|
||||
input: map[string]any{"id": float64(1)},
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonData(map[string]any{"id": float64(1)}),
|
||||
},
|
||||
{
|
||||
name: "error becomes json error",
|
||||
input: errors.New("boom"),
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonError(errors.New("boom")),
|
||||
},
|
||||
{
|
||||
name: "page data becomes page result",
|
||||
input: PageData([]any{"a"}, &sqls.Paging{Page: 1, Limit: 20, Total: 1}),
|
||||
wantStatus: http.StatusOK,
|
||||
want: *web.JsonPageData([]any{"a"}, &sqls.Paging{Page: 1, Limit: 20, Total: 1}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx, recorder := testContext()
|
||||
|
||||
WriteJSON(ctx, tt.input)
|
||||
|
||||
if recorder.Code != tt.wantStatus {
|
||||
t.Fatalf("status = %d, want %d", recorder.Code, tt.wantStatus)
|
||||
}
|
||||
var got web.JsonResult
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &got); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if got.Success != tt.want.Success || got.ErrorCode != tt.want.ErrorCode || got.Message != tt.want.Message {
|
||||
t.Fatalf("result = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteJSONLocalizesI18nErrors(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
ctx, recorder := testContext()
|
||||
i18nx.SetLocale(ctx, i18nx.LocaleEnUS)
|
||||
|
||||
WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0116"))
|
||||
|
||||
var got web.JsonResult
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &got); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if got.Message != "Conversation not found." {
|
||||
t.Fatalf("message = %q, want %q", got.Message, "Conversation not found.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteHttpStatusJSONUsesProvidedStatus(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
ctx, recorder := testContext()
|
||||
|
||||
WriteHttpStatusJSON(ctx, http.StatusUnauthorized, web.JsonErrorMsg("unauthorized"))
|
||||
|
||||
if recorder.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func testContext() (*gin.Context, *httptest.ResponseRecorder) {
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
return ctx, recorder
|
||||
}
|
||||
Reference in New Issue
Block a user