db 管理初始化

This commit is contained in:
wuchao 2024-11-27 16:15:27 +08:00
parent 00f5f1bde7
commit 2f915f225d
34 changed files with 7553 additions and 50 deletions

View File

@ -3,5 +3,5 @@ package common
const (
TOKEN_PRE = "player_token_"
TOKEN_Admin = "Admin_token_"
ADMIN_V1 = "/admin/api/v1"
ADMIN_OAUTH_V1 = "/admin/api/oauth/v1"
)

View File

@ -0,0 +1,27 @@
package backend
import (
"cron_admin/app/constants/errorcode"
"cron_admin/app/http/controllers"
"cron_admin/app/http/entities/backend"
"cron_admin/app/services/db_service"
"cron_admin/app/utils/helper"
"cron_admin/app/utils/mapstructure"
"github.com/gin-gonic/gin"
)
func DbList(c *gin.Context) {
request := controllers.GetRequest(c).(*backend.DbListRequest)
count, DbListInfo, err := db_service.DbList(request, request.Page, request.Limit)
if err != nil {
controllers.HandCodeRes(c, nil, errorcode.ParamError)
} else {
var DbListResponse []backend.DbListResponse
_ = mapstructure.DecodeWithTime(DbListInfo, &DbListResponse, helper.DefaultFormatLayout)
controllers.HandRes(c, gin.H{"data": DbListResponse, "count": count}, err)
}
}
func DbAdd(c *gin.Context) {
request := controllers.GetRequest(c).(*backend.DbAddRequest)
}

View File

@ -0,0 +1,23 @@
package backend
type DbListRequest struct {
Page int `json:"page" validate:"required" form:"page" example:"1"`
Limit int `json:"limit" validate:"required" form:"limit" example:"10"`
DbName string `json:"db_name" form:"db_name" example:""`
Status int `json:"status" form:"status" example:"1"`
DbType string `json:"db_type" form:"db_type" example:"mysql"`
}
type DbListResponse struct {
DbId string `json:"db_id"`
DbName string `json:"db_name"`
DbType string `json:"db_type"`
Status int `json:"status"`
DbPermission int `json:"db_permission"`
Source string `json:"source"`
Desc string `json:"desc"`
CreateTime string `json:"create_time"`
}
type DbAddRequest struct {
}

View File

@ -1,8 +1,13 @@
package requestmapping
import (
"cron_admin/app/constants/common"
"cron_admin/app/http/entities/backend"
)
var BackendRequestMap = map[string]func() interface{}{
//common.ADMIN_V1 + "/product/create": func() interface{} {
// return new(backend.ProductCreateRequest)
//},
common.ADMIN_OAUTH_V1 + "/sql/list": func() interface{} {
return new(backend.DbListRequest)
},
}

View File

@ -38,7 +38,7 @@ func RegisterAdminRoute(router *gin.Engine) {
//数据库管理
sql := v1.Group("/sql")
{
sql.GET("/list", backend.Empty)
sql.POST("/list", backend.DbList)
}
//任务
cmd := v1.Group("/cmd")

View File

@ -0,0 +1,30 @@
package db_service
import (
"cron_admin/app/http/entities/backend"
"cron_admin/app/models/crondbmodel"
"xorm.io/builder"
)
func DbList(request *backend.DbListRequest, page int, limit int) (count int64, DbListInfo []crondbmodel.CronDb, err error) {
conn := builder.NewCond()
if request.DbName != "" {
conn = conn.And(builder.Like{"DbName", request.DbName})
}
if request.DbType != "" {
conn = conn.And(builder.Like{"DbType", request.DbType})
}
if request.Status != 0 {
conn = conn.And(builder.Eq{"Status": request.Status})
}
session := crondbmodel.GetInstance().GetDb().Where(conn)
if page != 0 && limit != 0 {
session = session.Limit(limit, (page-1)*limit)
}
count, err = session.FindAndCount(&DbListInfo)
if err != nil {
return
}
return
}

View File

@ -0,0 +1,19 @@
package helper
import "github.com/duke-git/lancet/v2/slice"
func SliceConvertSlice[O int | int32 | int64 | int8, T int | int32 | int64 | int8](inputSlice []T) []O {
output := slice.Map(inputSlice, func(_ int, item T) O {
return O(item)
})
return output
}
// MergeInt64Slices 合并两个 []O 切片为一个新的 []O 切片
func MergeInt64Slices[O int | int32 | int64 | int8](a, b []O) []O {
length := len(a) + len(b)
merged := make([]O, length)
copy(merged, a)
copy(merged[len(a):], b)
return merged
}

View File

@ -0,0 +1,15 @@
package helper
import (
"fmt"
"testing"
)
func TestMergeInt64Slices(t *testing.T) {
v1 := []int64{1, 2}
v2 := []int64{4, 3}
v := MergeInt64Slices(v1, v2)
fmt.Printf("%v", v)
vv := append(v1, v2...)
fmt.Printf("%v", vv)
}

203
app/utils/helper/datetime.go Executable file
View File

@ -0,0 +1,203 @@
package helper
import (
"time"
"github.com/pkg/errors"
"google.golang.org/protobuf/types/known/timestamppb"
)
const (
DefaultParseFormatLayout = "2006-1-02 15:04:05" // 默认解析时间格式
DefaultFormatLayout = "2006-01-02 15:04:05" // 默认时间格式
MicroFormatLayout = "2006-01-02 15:04:05.000000" // 微秒时间格式
FormatLayout = "20060102150405" // 导出时间格式
)
func AfterMinutesUnix(minutes int64) int64 {
// 获取当前时间
currentTime := time.Now()
// 将当前时间增加5分钟
currentTime = currentTime.Add(time.Duration(minutes) * time.Minute)
// 转换为Unix时间戳
return currentTime.Unix()
}
func DateStringToTime(localTimeStr string) (time.Time, error) {
defer func() {
if err := recover(); err != nil {
LogErr(errors.New("时间转换错误:" + localTimeStr))
}
}()
if localTimeStr == "" {
return time.Time{}, nil
}
layouts := []string{
DefaultParseFormatLayout,
DefaultFormatLayout,
"2006-01-02 15:04",
"2006-1-02 15:04",
"2006-01-02",
"2006-1-02",
time.RFC3339Nano,
}
var (
localTime time.Time
err error
)
loc, _ := time.LoadLocation(GetTimeZone())
for _, layout := range layouts {
localTime, err = time.ParseInLocation(layout, localTimeStr, loc)
if err == nil {
break
}
}
if err != nil {
return time.Time{}, errors.Errorf("解析时间错误: %s", err.Error())
}
return localTime, nil
}
// TimeFormat 时间戳转时间格式
func TimeFormat(inTime *time.Time, layout ...string) string {
if inTime == nil {
return ""
}
if len(layout) == 0 {
layout = []string{DefaultFormatLayout}
}
return inTime.Format(layout[0])
}
// TimeStampFormat 时间戳转时间格式
func TimeStampFormat(timestamp int64, layout ...string) string {
if timestamp == 0 {
return ""
}
if len(layout) == 0 {
layout = []string{DefaultFormatLayout}
}
return time.Unix(timestamp, 0).Format(layout[0])
}
// Int32TimeStampFormat int32类型时间戳转时间格式
func Int32TimeStampFormat(timestamp int32, layout ...string) string {
return TimeStampFormat(int64(timestamp), layout...)
}
// IsoDateStringToTimeStamp iso格式字符串转时间戳
func IsoDateStringToTimeStamp(isoTimeStr string) (int64, error) {
isoTime, err := time.ParseInLocation(time.RFC3339Nano, isoTimeStr, time.Local)
if err != nil {
return 0, err
}
return isoTime.Unix(), nil
}
// LayOutDateStringToTimeStamp layout格式字符串转时间戳
func LayOutDateStringToTimeStamp(localTimeStr string) (int64, error) {
localTime, err := time.ParseInLocation(DefaultFormatLayout, localTimeStr, time.Local)
if err != nil {
return 0, err
}
return localTime.Unix(), nil
}
// DateStringToTimeStamp 字符串转时间戳(兼容格式)
func DateStringToTimeStamp(date string) (int64, error) {
u, err := IsoDateStringToTimeStamp(date)
if err != nil {
u, err = LayOutDateStringToTimeStamp(date)
}
return u, err
}
// DateStringToTimeStampInt32 字符串转时间戳(兼容格式)
func DateStringToTimeStampInt32(date string) (int32, error) {
u, err := DateStringToTimeStamp(date)
return int32(u), err
}
// BatchDateStringToTimeStampInt32 批量字符串转时间戳
func BatchDateStringToTimeStampInt32(dates []string) ([]int32, error) {
res := make([]int32, 0, len(dates))
for _, date := range dates {
u, err := DateStringToTimeStampInt32(date)
if err != nil {
return nil, err
}
res = append(res, u)
}
return res, nil
}
// GoogleTimeToString google proto时间转字符串
func GoogleTimeToString(date *timestamppb.Timestamp) string {
loc, _ := time.LoadLocation(GetTimeZone())
return date.AsTime().In(loc).Format(DefaultFormatLayout)
}
// GoogleTimeToMicroString google proto时间转字符串
func GoogleTimeToMicroString(date *timestamppb.Timestamp) string {
loc, _ := time.LoadLocation(GetTimeZone())
return date.AsTime().In(loc).Format(MicroFormatLayout)
}
func DateStringToTimeStampV2(localTimeStr string) (int64, error) {
if localTimeStr == "" {
return 0, nil
}
layouts := []string{
DefaultParseFormatLayout,
DefaultFormatLayout,
"2006-01-02 15:04",
"2006-1-02 15:04",
"2006-01-02",
"2006-1-02",
time.RFC3339Nano,
}
var (
localTime time.Time
err error
)
loc, _ := time.LoadLocation(GetTimeZone())
for _, layout := range layouts {
localTime, err = time.ParseInLocation(layout, localTimeStr, loc)
if err == nil {
break
}
}
if err != nil {
return 0, errors.Errorf("解析时间错误: %s", err.Error())
}
return localTime.Unix(), nil
}
// IsTimeStrBefore 时间比较 格式2006-01-02 15:04:05
func IsTimeStrBefore(beforeTime, afterTime string) bool {
time1, _ := time.Parse(DefaultFormatLayout, beforeTime)
time2, _ := time.Parse(DefaultFormatLayout, afterTime)
if time1.Before(time2) {
return true
}
return false
}
// IsTimeBefore 时间比较 格式2006-01-02 15:04:05
func IsTimeBefore(beforeTime, afterTime time.Time) bool {
if beforeTime.Before(afterTime) {
return true
}
return false
}

View File

@ -0,0 +1,14 @@
package helper
import (
"testing"
)
func TestDateStringToTime(t *testing.T) {
got, err := DateStringToTime("2024-01-01 00:00:00")
if err != nil {
t.Errorf("DateStringToTime() error = %v", err)
return
}
t.Log(got)
}

23
app/utils/helper/env.go Executable file
View File

@ -0,0 +1,23 @@
package helper
import "os"
const (
DefaultTimeZone = "Asia/Shanghai" // 时区
)
func GetEnv(name string) string {
return os.Getenv(name)
}
func GetEnvWithDefault(name string, defaultValue string) string {
value := GetEnv(name)
if value == "" {
return defaultValue
}
return value
}
func GetTimeZone() string {
return GetEnvWithDefault("TZ", DefaultTimeZone)
}

View File

@ -0,0 +1,69 @@
package helper
import (
"fmt"
"github.com/go-kratos/kratos/v2/transport/http"
"github.com/pkg/errors"
"github.com/xuri/excelize/v2"
"strconv"
)
func ImportFile(ctx http.Context) (map[string][]int32, error) {
// 获取上传的文件
file, _, err := ctx.Request().FormFile("file")
if err != nil {
return nil, fmt.Errorf("failed to get form file: %w", err)
}
defer file.Close()
// 解析Excel文件
f, err := excelize.OpenReader(file)
if err != nil {
return nil, fmt.Errorf("failed to open excel file: %w", err)
}
defer f.Close()
// 获取第一个工作表的名称
sheetNames := f.GetSheetList()
if len(sheetNames) == 0 {
return nil, fmt.Errorf("无效的excel未获取到对应的sheet")
}
// 获取所有行
rows, err := f.GetRows(sheetNames[0])
if err != nil {
return nil, fmt.Errorf("failed to get rows from Excel sheet: %w", err)
}
if len(rows) == 0 {
return nil, fmt.Errorf("无效的excel未获取到对应的记录数据")
}
actualHeaders := rows[0] // 获取第一行作为表头
var key int
for i, v := range actualHeaders {
if v == "ID" {
key = i
}
}
// 定义一个map来存储订单ID到SendInfo的映射
var ids []int32
// 遍历行并处理数据
for i, row := range rows {
if i == 0 {
continue // 跳过头栏
}
if row[key] != "" {
id, _ := strconv.Atoi(row[key])
ids = append(ids, int32(id))
}
}
if len(ids) == 0 {
return nil, errors.New("未获取到数据")
}
idsPb := map[string][]int32{"ids": ids}
return idsPb, nil
}

46
app/utils/helper/ip.go Normal file
View File

@ -0,0 +1,46 @@
package helper
import (
"context"
"github.com/go-kratos/kratos/v2/transport/http"
"net"
"strings"
)
// GetHeaderRealIP 获取header头中的真实IP
func GetHeaderRealIP(ctx context.Context) string {
serverContext, isOk := http.RequestFromServerContext(ctx)
if isOk {
return serverContext.Header.Get("X-Real-Ip")
}
return ""
}
// GetClientIP 获取客户端IP
func GetClientIP(r *http.Request) string {
xForwardedFor := r.Header.Get("X-Forwarded-For")
ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0])
if ip != "" {
return ip
}
ip = strings.TrimSpace(r.Header.Get("X-Real-Ip"))
if ip != "" {
return ip
}
if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
return ip
}
return ""
}
// IPString2Long 把ip字符串转为数值(ipv4)
func IPString2Long(ip string) uint {
b := net.ParseIP(ip).To4()
if b == nil {
return 0
}
return uint(b[3]) | uint(b[2])<<8 | uint(b[1])<<16 | uint(b[0])<<24
}

View File

@ -0,0 +1,16 @@
package helper
import (
"strconv"
"time"
"github.com/bwmarrin/snowflake"
)
func GenerateIDString() (string, error) {
node, err := snowflake.NewNode(time.Now().UnixMilli() % 1023)
if err != nil {
return "", err
}
return strconv.FormatInt(node.Generate().Int64(), 36), nil
}

View File

@ -0,0 +1,14 @@
package helper
import (
"testing"
)
func TestGenerateIDString(t *testing.T) {
s, err := GenerateIDString()
if err != nil {
t.Error(err)
return
}
t.Log(s)
}

94
app/utils/helper/string.go Executable file
View File

@ -0,0 +1,94 @@
package helper
import (
"math/rand"
"sort"
"strconv"
"strings"
"time"
"unicode"
)
func StringToInt64(s string) int64 {
i, _ := strconv.ParseInt(s, 10, 64)
return i
}
func StringToInt32(s string) int32 {
i, _ := strconv.ParseInt(s, 10, 64)
return int32(i)
}
// StringToInt32WithError 将字符串转换为int32
func StringToInt32WithError(s string) (int32, error) {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, err
}
return int32(i), nil
}
// StringToAsterisk 替换密钥中间字符为 *
func StringToAsterisk(value string) string {
length := len(value)
if length == 0 {
return ""
}
asterisk := "*"
if length < 4 {
asterisk = strings.Repeat(asterisk, length)
return strings.Replace(value, value, asterisk, 1)
}
offset := length / 4
asterisk = strings.Repeat(asterisk, length-offset*2)
return value[:offset] + asterisk + value[length-offset:]
}
func StrSuffixNumSort(in []string) []string {
sort.Slice(in, func(i, j int) bool {
return getSuffixNumber(in[i]) < getSuffixNumber(in[j])
})
var result []string
for _, str := range in {
result = append(result, str)
}
return result
}
// 获取字符串中最后面的后缀数字
func getSuffixNumber(s string) int {
parts := strings.Split(s, "_")
lastPart := parts[len(parts)-1] // 获取最后一个部分
numStr := ""
for i := len(lastPart) - 1; i >= 0; i-- {
if _, err := strconv.Atoi(string(lastPart[i])); err == nil {
numStr = string(lastPart[i]) + numStr
} else {
break
}
}
num, _ := strconv.Atoi(numStr)
return num
}
func GetRandomStr(length int) string {
rand.Seed(time.Now().UnixNano())
// 去掉容易搞错的字符例如0,1,l,L,0,O,o,I,i
const charset = "abcdefghjklmnpqrstuvwxyzABCDEFGHIJKMNPQRSTUVWXYZ123456789"
password := make([]byte, length)
for i := range password {
password[i] = charset[rand.Intn(len(charset))]
}
return string(password)
}
func ToTitleFirst(s string) string {
if s == "" {
return s
}
runes := []rune(s)
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}

58
app/utils/helper/string_test.go Executable file
View File

@ -0,0 +1,58 @@
package helper
import (
"fmt"
"testing"
)
// 测试 StringToInt32方法
func TestStringToInt32(t *testing.T) {
// 测试用例
type args struct {
s string
}
tests := []struct {
name string
args args
want int32
}{
{name: "测试1", args: args{s: "123"}, want: 123},
{name: "测试2", args: args{s: "123.123"}, want: 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 调用方法
if got := StringToInt32(tt.args.s); got != tt.want {
// 判断结果
t.Errorf("StringToInt32() = %v, want %v", got, tt.want)
}
})
}
}
func TestReplaceStar(t *testing.T) {
type args struct {
secretKey string
}
tests := []struct {
name string
args args
want string
}{
{name: "测试1", args: args{secretKey: "123456789"}, want: "12******89"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := StringToAsterisk(tt.args.secretKey)
t.Logf("StringToAsterisk() = %v, want %v \n", got, tt.want)
if got != tt.want {
t.Errorf("StringToAsterisk() = %v, want %v \n", got, tt.want)
}
})
}
}
func TestRandomPassword(t *testing.T) {
password := GetRandomStr(6)
fmt.Println("生成的随机密码为:", password)
}

118
app/utils/helper/util.go Normal file
View File

@ -0,0 +1,118 @@
package helper
import (
"crypto/md5"
"fmt"
"github.com/golang/protobuf/proto"
"net/url"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"time"
)
func Log(name string, msg ...interface{}) {
_, file, line, _ := runtime.Caller(1)
timeLayout := "2006-01-01 03:04:05" //转化所需模板
var datetime = time.Unix(time.Now().Unix(), 0).Format(timeLayout)
fmt.Println(name, msg, file, line, datetime)
}
func LogErr(err error) {
if err != nil {
Log("err", err.Error())
}
}
func Path(path string) (string, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
if err := os.MkdirAll(path, 0777); err != nil {
return "", err
}
}
return path, nil
}
func FileExists(filePath string) bool {
_, err := os.Stat(filePath)
return err == nil || os.IsExist(err)
}
// TrackTime 运行时间 defer TrackTime()()
func TrackTime() func() {
pre := time.Now()
return func() {
elapsed := time.Since(pre)
fmt.Println("elapsed:", elapsed)
}
}
// Ter 三目运算 Ter(true, 1, 2)
func Ter[T any](cond bool, a, b T) T {
if cond {
return a
}
return b
}
// get参转结构体
func ParseQueryStringToStruct(query string, target interface{}) error {
// 解析查询字符串
values, err := url.ParseQuery(query)
if err != nil {
return err
}
// 获取目标结构体的反射值
rv := reflect.ValueOf(target).Elem()
// 遍历查询字符串中的每个键值对
for key, values := range values {
// 假设每个查询参数只传递一个值(如果有多个值,这里只取第一个)
value := values[0]
// 查找目标结构体中对应的字段
field := rv.FieldByName(strings.Title(key)) // 假设字段名与查询参数名相同,但首字母大写
if !field.IsValid() || !field.CanSet() {
// 如果找不到对应的字段,或者字段不可设置,则跳过
continue
}
// 根据字段类型设置值
switch field.Kind() {
case reflect.String:
field.SetString(value)
case reflect.Int:
intValue, err := strconv.Atoi(value)
if err != nil {
return err
}
field.SetInt(int64(intValue))
// 可以根据需要添加更多类型处理
default:
// 不支持的类型,可以选择跳过或返回错误
continue
}
}
return nil
}
// GenerateMD5Hash 生成 MD5 的函数,接收一个实现了 proto.Message 接口的结构体
func GenerateMD5Hash(msg proto.Message) (string, error) {
// 使用 proto.Marshal 序列化结构体proto.Marshal 接受任何实现 proto.Message 接口的结构体
data, err := proto.Marshal(msg)
if err != nil {
return "", fmt.Errorf("failed to serialize message: %v", err)
}
// 计算 MD5
hash := md5.New()
hash.Write(data)
hashBytes := hash.Sum(nil)
// 返回 MD5 值的十六进制字符串
return fmt.Sprintf("%x", hashBytes), nil
}

View File

@ -0,0 +1,11 @@
package helper
import "regexp"
// chineseMobileFormMatcher 手机号格式正则匹配器
var chineseMobileFormMatcher = regexp.MustCompile(`^1\d{10}$`)
// IsMobileForm 手机号格式,不严谨的验证
func IsMobileForm(mobile string) bool {
return chineseMobileFormMatcher.MatchString(mobile)
}

View File

@ -0,0 +1,279 @@
package mapstructure
import (
"encoding"
"errors"
"fmt"
"net"
"reflect"
"strconv"
"strings"
"time"
)
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
// Create variables here so we can reference them with the reflect pkg
var f1 DecodeHookFuncType
var f2 DecodeHookFuncKind
var f3 DecodeHookFuncValue
// Fill in the variables into this interface and the rest is done
// automatically using the reflect package.
potential := []interface{}{f1, f2, f3}
v := reflect.ValueOf(h)
vt := v.Type()
for _, raw := range potential {
pt := reflect.ValueOf(raw).Type()
if vt.ConvertibleTo(pt) {
return v.Convert(pt).Interface()
}
}
return nil
}
// DecodeHookExec executes the given decode hook. This should be used
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
// that took reflect.Kind instead of reflect.Type.
func DecodeHookExec(
raw DecodeHookFunc,
from reflect.Value, to reflect.Value) (interface{}, error) {
switch f := typedDecodeHook(raw).(type) {
case DecodeHookFuncType:
return f(from.Type(), to.Type(), from.Interface())
case DecodeHookFuncKind:
return f(from.Kind(), to.Kind(), from.Interface())
case DecodeHookFuncValue:
return f(from, to)
default:
return nil, errors.New("invalid decode hook signature")
}
}
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
// automatically composes multiple DecodeHookFuncs.
//
// The composed funcs are called in order, with the result of the
// previous transformation.
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
var err error
data := f.Interface()
newFrom := f
for _, f1 := range fs {
data, err = DecodeHookExec(f1, newFrom, t)
if err != nil {
return nil, err
}
newFrom = reflect.ValueOf(data)
}
return data, nil
}
}
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
return func(a, b reflect.Value) (interface{}, error) {
var allErrs string
var out interface{}
var err error
for _, f := range ff {
out, err = DecodeHookExec(f, a, b)
if err != nil {
allErrs += err.Error() + "\n"
continue
}
return out, nil
}
return nil, errors.New(allErrs)
}
}
// StringToSliceHookFunc returns a DecodeHookFunc that converts
// string to []string by splitting on the given sep.
func StringToSliceHookFunc(sep string) DecodeHookFunc {
return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
if f != reflect.String || t != reflect.Slice {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
return strings.Split(raw, sep), nil
}
}
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
// strings to time.Duration.
func StringToTimeDurationHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Duration(5)) {
return data, nil
}
// Convert it by parsing
return time.ParseDuration(data.(string))
}
}
// StringToIPHookFunc returns a DecodeHookFunc that converts
// strings to net.IP
func StringToIPHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IP{}) {
return data, nil
}
// Convert it by parsing
ip := net.ParseIP(data.(string))
if ip == nil {
return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
}
return ip, nil
}
}
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
// strings to net.IPNet
func StringToIPNetHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IPNet{}) {
return data, nil
}
// Convert it by parsing
_, net, err := net.ParseCIDR(data.(string))
return net, err
}
}
// StringToTimeHookFunc returns a DecodeHookFunc that converts
// strings to time.Time.
func StringToTimeHookFunc(layout string) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Time{}) {
return data, nil
}
// Convert it by parsing
return time.Parse(layout, data.(string))
}
}
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
// the decoder.
//
// Note that this is significantly different from the WeaklyTypedInput option
// of the DecoderConfig.
func WeaklyTypedHook(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
dataVal := reflect.ValueOf(data)
switch t {
case reflect.String:
switch f {
case reflect.Bool:
if dataVal.Bool() {
return "1", nil
}
return "0", nil
case reflect.Float32:
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
case reflect.Int:
return strconv.FormatInt(dataVal.Int(), 10), nil
case reflect.Slice:
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
if elemKind == reflect.Uint8 {
return string(dataVal.Interface().([]uint8)), nil
}
case reflect.Uint:
return strconv.FormatUint(dataVal.Uint(), 10), nil
}
}
return data, nil
}
func RecursiveStructToMapHookFunc() DecodeHookFunc {
return func(f reflect.Value, t reflect.Value) (interface{}, error) {
if f.Kind() != reflect.Struct {
return f.Interface(), nil
}
var i interface{} = struct{}{}
if t.Type() != reflect.TypeOf(&i).Elem() {
return f.Interface(), nil
}
m := make(map[string]interface{})
t.Set(reflect.ValueOf(m))
return f.Interface(), nil
}
}
// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
// strings to the UnmarshalText function, when the target type
// implements the encoding.TextUnmarshaler interface
func TextUnmarshallerHookFunc() DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
result := reflect.New(t).Interface()
unmarshaller, ok := result.(encoding.TextUnmarshaler)
if !ok {
return data, nil
}
if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil {
return nil, err
}
return result, nil
}
}

View File

@ -0,0 +1,567 @@
package mapstructure
import (
"errors"
"math/big"
"net"
"reflect"
"testing"
"time"
)
func TestComposeDecodeHookFunc(t *testing.T) {
f1 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return data.(string) + "foo", nil
}
f2 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return data.(string) + "bar", nil
}
f := ComposeDecodeHookFunc(f1, f2)
result, err := DecodeHookExec(
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
if err != nil {
t.Fatalf("bad: %s", err)
}
if result.(string) != "foobar" {
t.Fatalf("bad: %#v", result)
}
}
func TestComposeDecodeHookFunc_err(t *testing.T) {
f1 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
return nil, errors.New("foo")
}
f2 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
panic("NOPE")
}
f := ComposeDecodeHookFunc(f1, f2)
_, err := DecodeHookExec(
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
if err.Error() != "foo" {
t.Fatalf("bad: %s", err)
}
}
func TestComposeDecodeHookFunc_kinds(t *testing.T) {
var f2From reflect.Kind
f1 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return int(42), nil
}
f2 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
f2From = f
return data, nil
}
f := ComposeDecodeHookFunc(f1, f2)
_, err := DecodeHookExec(
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
if err != nil {
t.Fatalf("bad: %s", err)
}
if f2From != reflect.Int {
t.Fatalf("bad: %#v", f2From)
}
}
func TestOrComposeDecodeHookFunc(t *testing.T) {
f1 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return data.(string) + "foo", nil
}
f2 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return data.(string) + "bar", nil
}
f := OrComposeDecodeHookFunc(f1, f2)
result, err := DecodeHookExec(
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
if err != nil {
t.Fatalf("bad: %s", err)
}
if result.(string) != "foo" {
t.Fatalf("bad: %#v", result)
}
}
func TestOrComposeDecodeHookFunc_correctValueIsLast(t *testing.T) {
f1 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return nil, errors.New("f1 error")
}
f2 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return nil, errors.New("f2 error")
}
f3 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return data.(string) + "bar", nil
}
f := OrComposeDecodeHookFunc(f1, f2, f3)
result, err := DecodeHookExec(
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
if err != nil {
t.Fatalf("bad: %s", err)
}
if result.(string) != "bar" {
t.Fatalf("bad: %#v", result)
}
}
func TestOrComposeDecodeHookFunc_err(t *testing.T) {
f1 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return nil, errors.New("f1 error")
}
f2 := func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
return nil, errors.New("f2 error")
}
f := OrComposeDecodeHookFunc(f1, f2)
_, err := DecodeHookExec(
f, reflect.ValueOf(""), reflect.ValueOf([]byte("")))
if err == nil {
t.Fatalf("bad: should return an error")
}
if err.Error() != "f1 error\nf2 error\n" {
t.Fatalf("bad: %s", err)
}
}
func TestComposeDecodeHookFunc_safe_nofuncs(t *testing.T) {
f := ComposeDecodeHookFunc()
type myStruct2 struct {
MyInt int
}
type myStruct1 struct {
Blah map[string]myStruct2
}
src := &myStruct1{Blah: map[string]myStruct2{
"test": {
MyInt: 1,
},
}}
dst := &myStruct1{}
dConf := &DecoderConfig{
Result: dst,
ErrorUnused: true,
DecodeHook: f,
}
d, err := NewDecoder(dConf)
if err != nil {
t.Fatal(err)
}
err = d.Decode(src)
if err != nil {
t.Fatal(err)
}
}
func TestStringToSliceHookFunc(t *testing.T) {
f := StringToSliceHookFunc(",")
strValue := reflect.ValueOf("42")
sliceValue := reflect.ValueOf([]byte("42"))
cases := []struct {
f, t reflect.Value
result interface{}
err bool
}{
{sliceValue, sliceValue, []byte("42"), false},
{strValue, strValue, "42", false},
{
reflect.ValueOf("foo,bar,baz"),
sliceValue,
[]string{"foo", "bar", "baz"},
false,
},
{
reflect.ValueOf(""),
sliceValue,
[]string{},
false,
},
}
for i, tc := range cases {
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}
func TestStringToTimeDurationHookFunc(t *testing.T) {
f := StringToTimeDurationHookFunc()
timeValue := reflect.ValueOf(time.Duration(5))
strValue := reflect.ValueOf("")
cases := []struct {
f, t reflect.Value
result interface{}
err bool
}{
{reflect.ValueOf("5s"), timeValue, 5 * time.Second, false},
{reflect.ValueOf("5"), timeValue, time.Duration(0), true},
{reflect.ValueOf("5"), strValue, "5", false},
}
for i, tc := range cases {
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}
func TestStringToTimeHookFunc(t *testing.T) {
strValue := reflect.ValueOf("5")
timeValue := reflect.ValueOf(time.Time{})
cases := []struct {
f, t reflect.Value
layout string
result interface{}
err bool
}{
{reflect.ValueOf("2006-01-02T15:04:05Z"), timeValue, time.RFC3339,
time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), false},
{strValue, timeValue, time.RFC3339, time.Time{}, true},
{strValue, strValue, time.RFC3339, "5", false},
}
for i, tc := range cases {
f := StringToTimeHookFunc(tc.layout)
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}
func TestStringToIPHookFunc(t *testing.T) {
strValue := reflect.ValueOf("5")
ipValue := reflect.ValueOf(net.IP{})
cases := []struct {
f, t reflect.Value
result interface{}
err bool
}{
{reflect.ValueOf("1.2.3.4"), ipValue,
net.IPv4(0x01, 0x02, 0x03, 0x04), false},
{strValue, ipValue, net.IP{}, true},
{strValue, strValue, "5", false},
}
for i, tc := range cases {
f := StringToIPHookFunc()
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}
func TestStringToIPNetHookFunc(t *testing.T) {
strValue := reflect.ValueOf("5")
ipNetValue := reflect.ValueOf(net.IPNet{})
var nilNet *net.IPNet = nil
cases := []struct {
f, t reflect.Value
result interface{}
err bool
}{
{reflect.ValueOf("1.2.3.4/24"), ipNetValue,
&net.IPNet{
IP: net.IP{0x01, 0x02, 0x03, 0x00},
Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
}, false},
{strValue, ipNetValue, nilNet, true},
{strValue, strValue, "5", false},
}
for i, tc := range cases {
f := StringToIPNetHookFunc()
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}
func TestWeaklyTypedHook(t *testing.T) {
var f DecodeHookFunc = WeaklyTypedHook
strValue := reflect.ValueOf("")
cases := []struct {
f, t reflect.Value
result interface{}
err bool
}{
// TO STRING
{
reflect.ValueOf(false),
strValue,
"0",
false,
},
{
reflect.ValueOf(true),
strValue,
"1",
false,
},
{
reflect.ValueOf(float32(7)),
strValue,
"7",
false,
},
{
reflect.ValueOf(int(7)),
strValue,
"7",
false,
},
{
reflect.ValueOf([]uint8("foo")),
strValue,
"foo",
false,
},
{
reflect.ValueOf(uint(7)),
strValue,
"7",
false,
},
}
for i, tc := range cases {
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}
func TestStructToMapHookFuncTabled(t *testing.T) {
var f DecodeHookFunc = RecursiveStructToMapHookFunc()
type b struct {
TestKey string
}
type a struct {
Sub b
}
testStruct := a{
Sub: b{
TestKey: "testval",
},
}
testMap := map[string]interface{}{
"Sub": map[string]interface{}{
"TestKey": "testval",
},
}
cases := []struct {
name string
receiver interface{}
input interface{}
expected interface{}
err bool
}{
{
"map receiver",
func() interface{} {
var res map[string]interface{}
return &res
}(),
testStruct,
&testMap,
false,
},
{
"interface receiver",
func() interface{} {
var res interface{}
return &res
}(),
testStruct,
func() interface{} {
var exp interface{} = testMap
return &exp
}(),
false,
},
{
"slice receiver errors",
func() interface{} {
var res []string
return &res
}(),
testStruct,
new([]string),
true,
},
{
"slice to slice - no change",
func() interface{} {
var res []string
return &res
}(),
[]string{"a", "b"},
&[]string{"a", "b"},
false,
},
{
"string to string - no change",
func() interface{} {
var res string
return &res
}(),
"test",
func() *string {
s := "test"
return &s
}(),
false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cfg := &DecoderConfig{
DecodeHook: f,
Result: tc.receiver,
}
d, err := NewDecoder(cfg)
if err != nil {
t.Fatalf("unexpected err %#v", err)
}
err = d.Decode(tc.input)
if tc.err != (err != nil) {
t.Fatalf("expected err %#v", err)
}
if !reflect.DeepEqual(tc.expected, tc.receiver) {
t.Fatalf("expected %#v, got %#v",
tc.expected, tc.receiver)
}
})
}
}
func TestTextUnmarshallerHookFunc(t *testing.T) {
cases := []struct {
f, t reflect.Value
result interface{}
err bool
}{
{reflect.ValueOf("42"), reflect.ValueOf(big.Int{}), big.NewInt(42), false},
{reflect.ValueOf("invalid"), reflect.ValueOf(big.Int{}), nil, true},
{reflect.ValueOf("5"), reflect.ValueOf("5"), "5", false},
}
for i, tc := range cases {
f := TextUnmarshallerHookFunc()
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}

View File

@ -0,0 +1,50 @@
package mapstructure
import (
"errors"
"fmt"
"sort"
"strings"
)
// Error implements the error interface and can represents multiple
// errors that occur in the course of a single decode.
type Error struct {
Errors []string
}
func (e *Error) Error() string {
points := make([]string, len(e.Errors))
for i, err := range e.Errors {
points[i] = fmt.Sprintf("* %s", err)
}
sort.Strings(points)
return fmt.Sprintf(
"%d error(s) decoding:\n\n%s",
len(e.Errors), strings.Join(points, "\n"))
}
// WrappedErrors implements the errwrap.Wrapper interface to make this
// return value more useful with the errwrap and go-multierror libraries.
func (e *Error) WrappedErrors() []error {
if e == nil {
return nil
}
result := make([]error, len(e.Errors))
for i, e := range e.Errors {
result[i] = errors.New(e)
}
return result
}
func appendErrors(errors []string, err error) []string {
switch e := err.(type) {
case *Error:
return append(errors, e.Errors...)
default:
return append(errors, e.Error())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,285 @@
package mapstructure
import (
"encoding/json"
"testing"
)
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
func Benchmark_Decode(b *testing.B) {
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
},
}
var result Person
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
// decodeViaJSON takes the map data and passes it through encoding/json to convert it into the
// given Go native structure pointed to by v. v must be a pointer to a struct.
func decodeViaJSON(data interface{}, v interface{}) error {
// Perform the task by simply marshalling the input into JSON,
// then unmarshalling it into target native Go struct.
b, err := json.Marshal(data)
if err != nil {
return err
}
return json.Unmarshal(b, v)
}
func Benchmark_DecodeViaJSON(b *testing.B) {
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
},
}
var result Person
for i := 0; i < b.N; i++ {
decodeViaJSON(input, &result)
}
}
func Benchmark_JSONUnmarshal(b *testing.B) {
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
},
}
inputB, err := json.Marshal(input)
if err != nil {
b.Fatal("Failed to marshal test input:", err)
}
var result Person
for i := 0; i < b.N; i++ {
json.Unmarshal(inputB, &result)
}
}
func Benchmark_DecodeBasic(b *testing.B) {
input := map[string]interface{}{
"vstring": "foo",
"vint": 42,
"Vuint": 42,
"vbool": true,
"Vfloat": 42.42,
"vsilent": true,
"vdata": 42,
"vjsonInt": json.Number("1234"),
"vjsonFloat": json.Number("1234.5"),
"vjsonNumber": json.Number("1234.5"),
}
for i := 0; i < b.N; i++ {
var result Basic
Decode(input, &result)
}
}
func Benchmark_DecodeEmbedded(b *testing.B) {
input := map[string]interface{}{
"vstring": "foo",
"Basic": map[string]interface{}{
"vstring": "innerfoo",
},
"vunique": "bar",
}
var result Embedded
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeTypeConversion(b *testing.B) {
input := map[string]interface{}{
"IntToFloat": 42,
"IntToUint": 42,
"IntToBool": 1,
"IntToString": 42,
"UintToInt": 42,
"UintToFloat": 42,
"UintToBool": 42,
"UintToString": 42,
"BoolToInt": true,
"BoolToUint": true,
"BoolToFloat": true,
"BoolToString": true,
"FloatToInt": 42.42,
"FloatToUint": 42.42,
"FloatToBool": 42.42,
"FloatToString": 42.42,
"StringToInt": "42",
"StringToUint": "42",
"StringToBool": "1",
"StringToFloat": "42.42",
"SliceToMap": []interface{}{},
"MapToSlice": map[string]interface{}{},
}
var resultStrict TypeConversionResult
for i := 0; i < b.N; i++ {
Decode(input, &resultStrict)
}
}
func Benchmark_DecodeMap(b *testing.B) {
input := map[string]interface{}{
"vfoo": "foo",
"vother": map[interface{}]interface{}{
"foo": "foo",
"bar": "bar",
},
}
var result Map
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeMapOfStruct(b *testing.B) {
input := map[string]interface{}{
"value": map[string]interface{}{
"foo": map[string]string{"vstring": "one"},
"bar": map[string]string{"vstring": "two"},
},
}
var result MapOfStruct
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeSlice(b *testing.B) {
input := map[string]interface{}{
"vfoo": "foo",
"vbar": []string{"foo", "bar", "baz"},
}
var result Slice
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeSliceOfStruct(b *testing.B) {
input := map[string]interface{}{
"value": []map[string]interface{}{
{"vstring": "one"},
{"vstring": "two"},
},
}
var result SliceOfStruct
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}
func Benchmark_DecodeWeaklyTypedInput(b *testing.B) {
// This input can come from anywhere, but typically comes from
// something like decoding JSON, generated by a weakly typed language
// such as PHP.
input := map[string]interface{}{
"name": 123, // number => string
"age": "42", // string => number
"emails": map[string]interface{}{}, // empty map => empty array
}
var result Person
config := &DecoderConfig{
WeaklyTypedInput: true,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
decoder.Decode(input)
}
}
func Benchmark_DecodeMetadata(b *testing.B) {
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"email": "foo@bar.com",
}
var md Metadata
var result Person
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
for i := 0; i < b.N; i++ {
decoder.Decode(input)
}
}
func Benchmark_DecodeMetadataEmbedded(b *testing.B) {
input := map[string]interface{}{
"vstring": "foo",
"vunique": "bar",
}
var md Metadata
var result EmbeddedSquash
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
b.Fatalf("err: %s", err)
}
for i := 0; i < b.N; i++ {
decoder.Decode(input)
}
}
func Benchmark_DecodeTagged(b *testing.B) {
input := map[string]interface{}{
"foo": "bar",
"bar": "value",
}
var result Tagged
for i := 0; i < b.N; i++ {
Decode(input, &result)
}
}

View File

@ -0,0 +1,627 @@
package mapstructure
import (
"reflect"
"testing"
"time"
)
// GH-1, GH-10, GH-96
func TestDecode_NilValue(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in interface{}
target interface{}
out interface{}
metaKeys []string
metaUnused []string
}{
{
"all nil",
&map[string]interface{}{
"vfoo": nil,
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "", Vother: nil},
[]string{"Vfoo", "Vother"},
[]string{},
},
{
"partial nil",
&map[string]interface{}{
"vfoo": "baz",
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "baz", Vother: nil},
[]string{"Vfoo", "Vother"},
[]string{},
},
{
"partial decode",
&map[string]interface{}{
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "foo", Vother: nil},
[]string{"Vother"},
[]string{},
},
{
"unused values",
&map[string]interface{}{
"vbar": "bar",
"vfoo": nil,
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "", Vother: nil},
[]string{"Vfoo", "Vother"},
[]string{"vbar"},
},
{
"map interface all nil",
&map[interface{}]interface{}{
"vfoo": nil,
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "", Vother: nil},
[]string{"Vfoo", "Vother"},
[]string{},
},
{
"map interface partial nil",
&map[interface{}]interface{}{
"vfoo": "baz",
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "baz", Vother: nil},
[]string{"Vfoo", "Vother"},
[]string{},
},
{
"map interface partial decode",
&map[interface{}]interface{}{
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "foo", Vother: nil},
[]string{"Vother"},
[]string{},
},
{
"map interface unused values",
&map[interface{}]interface{}{
"vbar": "bar",
"vfoo": nil,
"vother": nil,
},
&Map{Vfoo: "foo", Vother: map[string]string{"foo": "bar"}},
&Map{Vfoo: "", Vother: nil},
[]string{"Vfoo", "Vother"},
[]string{"vbar"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
config := &DecoderConfig{
Metadata: new(Metadata),
Result: tc.target,
ZeroFields: true,
}
decoder, err := NewDecoder(config)
if err != nil {
t.Fatalf("should not error: %s", err)
}
err = decoder.Decode(tc.in)
if err != nil {
t.Fatalf("should not error: %s", err)
}
if !reflect.DeepEqual(tc.out, tc.target) {
t.Fatalf("%q: TestDecode_NilValue() expected: %#v, got: %#v", tc.name, tc.out, tc.target)
}
if !reflect.DeepEqual(tc.metaKeys, config.Metadata.Keys) {
t.Fatalf("%q: Metadata.Keys mismatch expected: %#v, got: %#v", tc.name, tc.metaKeys, config.Metadata.Keys)
}
if !reflect.DeepEqual(tc.metaUnused, config.Metadata.Unused) {
t.Fatalf("%q: Metadata.Unused mismatch expected: %#v, got: %#v", tc.name, tc.metaUnused, config.Metadata.Unused)
}
})
}
}
// #48
func TestNestedTypePointerWithDefaults(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vbar": map[string]interface{}{
"vstring": "foo",
"vint": 42,
"vbool": true,
},
}
result := NestedPointer{
Vbar: &Basic{
Vuint: 42,
},
}
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
if result.Vfoo != "foo" {
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
}
if result.Vbar.Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
}
if result.Vbar.Vint != 42 {
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
}
if result.Vbar.Vbool != true {
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
}
if result.Vbar.Vextra != "" {
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
}
// this is the error
if result.Vbar.Vuint != 42 {
t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
}
}
type NestedSlice struct {
Vfoo string
Vbars []Basic
Vempty []Basic
}
// #48
func TestNestedTypeSliceWithDefaults(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vbars": []map[string]interface{}{
{"vstring": "foo", "vint": 42, "vbool": true},
{"vint": 42, "vbool": true},
},
"vempty": []map[string]interface{}{
{"vstring": "foo", "vint": 42, "vbool": true},
{"vint": 42, "vbool": true},
},
}
result := NestedSlice{
Vbars: []Basic{
{Vuint: 42},
{Vstring: "foo"},
},
}
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
if result.Vfoo != "foo" {
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
}
if result.Vbars[0].Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Vbars[0].Vstring)
}
// this is the error
if result.Vbars[0].Vuint != 42 {
t.Errorf("vuint value should be 42: %#v", result.Vbars[0].Vuint)
}
}
// #48 workaround
func TestNestedTypeWithDefaults(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"vfoo": "foo",
"vbar": map[string]interface{}{
"vstring": "foo",
"vint": 42,
"vbool": true,
},
}
result := Nested{
Vbar: Basic{
Vuint: 42,
},
}
err := Decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
if result.Vfoo != "foo" {
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
}
if result.Vbar.Vstring != "foo" {
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
}
if result.Vbar.Vint != 42 {
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
}
if result.Vbar.Vbool != true {
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
}
if result.Vbar.Vextra != "" {
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
}
// this is the error
if result.Vbar.Vuint != 42 {
t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
}
}
// #67 panic() on extending slices (decodeSlice with disabled ZeroValues)
func TestDecodeSliceToEmptySliceWOZeroing(t *testing.T) {
t.Parallel()
type TestStruct struct {
Vfoo []string
}
decode := func(m interface{}, rawVal interface{}) error {
config := &DecoderConfig{
Metadata: nil,
Result: rawVal,
ZeroFields: false,
}
decoder, err := NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(m)
}
{
input := map[string]interface{}{
"vfoo": []string{"1"},
}
result := &TestStruct{}
err := decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
}
{
input := map[string]interface{}{
"vfoo": []string{"1"},
}
result := &TestStruct{
Vfoo: []string{},
}
err := decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
}
{
input := map[string]interface{}{
"vfoo": []string{"2", "3"},
}
result := &TestStruct{
Vfoo: []string{"1"},
}
err := decode(input, &result)
if err != nil {
t.Fatalf("got an err: %s", err.Error())
}
}
}
// #70
func TestNextSquashMapstructure(t *testing.T) {
data := &struct {
Level1 struct {
Level2 struct {
Foo string
} `mapstructure:",squash"`
} `mapstructure:",squash"`
}{}
err := Decode(map[interface{}]interface{}{"foo": "baz"}, &data)
if err != nil {
t.Fatalf("should not error: %s", err)
}
if data.Level1.Level2.Foo != "baz" {
t.Fatal("value should be baz")
}
}
type ImplementsInterfacePointerReceiver struct {
Name string
}
func (i *ImplementsInterfacePointerReceiver) DoStuff() {}
type ImplementsInterfaceValueReceiver string
func (i ImplementsInterfaceValueReceiver) DoStuff() {}
// GH-140 Type error when using DecodeHook to decode into interface
func TestDecode_DecodeHookInterface(t *testing.T) {
t.Parallel()
type Interface interface {
DoStuff()
}
type DecodeIntoInterface struct {
Test Interface
}
testData := map[string]string{"test": "test"}
stringToPointerInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
if from.Kind() != reflect.String {
return data, nil
}
if to != reflect.TypeOf((*Interface)(nil)).Elem() {
return data, nil
}
// Ensure interface is satisfied
var impl Interface = &ImplementsInterfacePointerReceiver{data.(string)}
return impl, nil
}
stringToValueInterfaceDecodeHook := func(from, to reflect.Type, data interface{}) (interface{}, error) {
if from.Kind() != reflect.String {
return data, nil
}
if to != reflect.TypeOf((*Interface)(nil)).Elem() {
return data, nil
}
// Ensure interface is satisfied
var impl Interface = ImplementsInterfaceValueReceiver(data.(string))
return impl, nil
}
{
decodeInto := new(DecodeIntoInterface)
decoder, _ := NewDecoder(&DecoderConfig{
DecodeHook: stringToPointerInterfaceDecodeHook,
Result: decodeInto,
})
err := decoder.Decode(testData)
if err != nil {
t.Fatalf("Decode returned error: %s", err)
}
expected := &ImplementsInterfacePointerReceiver{"test"}
if !reflect.DeepEqual(decodeInto.Test, expected) {
t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
}
}
{
decodeInto := new(DecodeIntoInterface)
decoder, _ := NewDecoder(&DecoderConfig{
DecodeHook: stringToValueInterfaceDecodeHook,
Result: decodeInto,
})
err := decoder.Decode(testData)
if err != nil {
t.Fatalf("Decode returned error: %s", err)
}
expected := ImplementsInterfaceValueReceiver("test")
if !reflect.DeepEqual(decodeInto.Test, expected) {
t.Fatalf("expected: %#v (%T), got: %#v (%T)", decodeInto.Test, decodeInto.Test, expected, expected)
}
}
}
// #103 Check for data type before trying to access its composants prevent a panic error
// in decodeSlice
func TestDecodeBadDataTypeInSlice(t *testing.T) {
t.Parallel()
input := map[string]interface{}{
"Toto": "titi",
}
result := []struct {
Toto string
}{}
if err := Decode(input, &result); err == nil {
t.Error("An error was expected, got nil")
}
}
// #202 Ensure that intermediate maps in the struct -> struct decode process are settable
// and not just the elements within them.
func TestDecodeIntermediateMapsSettable(t *testing.T) {
type Timestamp struct {
Seconds int64
Nanos int32
}
type TsWrapper struct {
Timestamp *Timestamp
}
type TimeWrapper struct {
Timestamp time.Time
}
input := TimeWrapper{
Timestamp: time.Unix(123456789, 987654),
}
expected := TsWrapper{
Timestamp: &Timestamp{
Seconds: 123456789,
Nanos: 987654,
},
}
timePtrType := reflect.TypeOf((*time.Time)(nil))
mapStrInfType := reflect.TypeOf((map[string]interface{})(nil))
var actual TsWrapper
decoder, err := NewDecoder(&DecoderConfig{
Result: &actual,
DecodeHook: func(from, to reflect.Type, data interface{}) (interface{}, error) {
if from == timePtrType && to == mapStrInfType {
ts := data.(*time.Time)
nanos := ts.UnixNano()
seconds := nanos / 1000000000
nanos = nanos % 1000000000
return &map[string]interface{}{
"Seconds": seconds,
"Nanos": int32(nanos),
}, nil
}
return data, nil
},
})
if err != nil {
t.Fatalf("failed to create decoder: %v", err)
}
if err := decoder.Decode(&input); err != nil {
t.Fatalf("failed to decode input: %v", err)
}
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("expected: %#[1]v (%[1]T), got: %#[2]v (%[2]T)", expected, actual)
}
}
// GH-206: decodeInt throws an error for an empty string
func TestDecode_weakEmptyStringToInt(t *testing.T) {
input := map[string]interface{}{
"StringToInt": "",
"StringToUint": "",
"StringToBool": "",
"StringToFloat": "",
}
expectedResultWeak := TypeConversionResult{
StringToInt: 0,
StringToUint: 0,
StringToBool: false,
StringToFloat: 0,
}
// Test weak type conversion
var resultWeak TypeConversionResult
err := WeakDecode(input, &resultWeak)
if err != nil {
t.Fatalf("got an err: %s", err)
}
if !reflect.DeepEqual(resultWeak, expectedResultWeak) {
t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
}
}
// GH-228: Squash cause *time.Time set to zero
func TestMapSquash(t *testing.T) {
type AA struct {
T *time.Time
}
type A struct {
AA
}
v := time.Now()
in := &AA{
T: &v,
}
out := &A{}
d, err := NewDecoder(&DecoderConfig{
Squash: true,
Result: out,
})
if err != nil {
t.Fatalf("err: %s", err)
}
if err := d.Decode(in); err != nil {
t.Fatalf("err: %s", err)
}
// these failed
if !v.Equal(*out.T) {
t.Fatal("expected equal")
}
if out.T.IsZero() {
t.Fatal("expected false")
}
}
// GH-238: Empty key name when decoding map from struct with only omitempty flag
func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) {
type Struct struct {
Username string `mapstructure:",omitempty"`
Age int `mapstructure:",omitempty"`
}
s := Struct{
Username: "Joe",
}
var m map[string]interface{}
if err := Decode(s, &m); err != nil {
t.Fatal(err)
}
if len(m) != 1 {
t.Fatalf("fail: %#v", m)
}
if m["Username"] != "Joe" {
t.Fatalf("fail: %#v", m)
}
}

View File

@ -0,0 +1,256 @@
package mapstructure
import (
"fmt"
)
func ExampleDecode() {
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the
// struct initially.
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
},
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}
}
func ExampleDecode_errors() {
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the
// struct initially.
input := map[string]interface{}{
"name": 123,
"age": "bad value",
"emails": []int{1, 2, 3},
}
var result Person
err := Decode(input, &result)
if err == nil {
panic("should have an error")
}
fmt.Println(err.Error())
// Output:
// 5 error(s) decoding:
//
// * 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'
// * 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'
// * 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'
// * 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'
// * 'Name' expected type 'string', got unconvertible type 'int', value: '123'
}
func ExampleDecode_metadata() {
type Person struct {
Name string
Age int
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the
// struct initially.
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"email": "foo@bar.com",
}
// For metadata, we make a more advanced DecoderConfig so we can
// more finely configure the decoder that is used. In this case, we
// just tell the decoder we want to track metadata.
var md Metadata
var result Person
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
if err := decoder.Decode(input); err != nil {
panic(err)
}
fmt.Printf("Unused keys: %#v", md.Unused)
// Output:
// Unused keys: []string{"email"}
}
func ExampleDecode_weaklyTypedInput() {
type Person struct {
Name string
Age int
Emails []string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON, generated by a weakly typed language
// such as PHP.
input := map[string]interface{}{
"name": 123, // number => string
"age": "42", // string => number
"emails": map[string]interface{}{}, // empty map => empty array
}
var result Person
config := &DecoderConfig{
WeaklyTypedInput: true,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
err = decoder.Decode(input)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}}
}
func ExampleDecode_tags() {
// Note that the mapstructure tags defined in the struct type
// can indicate which fields the values are mapped to.
type Person struct {
Name string `mapstructure:"person_name"`
Age int `mapstructure:"person_age"`
}
input := map[string]interface{}{
"person_name": "Mitchell",
"person_age": 91,
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91}
}
func ExampleDecode_embeddedStruct() {
// Squashing multiple embedded structs is allowed using the squash tag.
// This is demonstrated by creating a composite struct of multiple types
// and decoding into it. In this case, a person can carry with it both
// a Family and a Location, as well as their own FirstName.
type Family struct {
LastName string
}
type Location struct {
City string
}
type Person struct {
Family `mapstructure:",squash"`
Location `mapstructure:",squash"`
FirstName string
}
input := map[string]interface{}{
"FirstName": "Mitchell",
"LastName": "Hashimoto",
"City": "San Francisco",
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%s %s, %s", result.FirstName, result.LastName, result.City)
// Output:
// Mitchell Hashimoto, San Francisco
}
func ExampleDecode_remainingData() {
// Note that the mapstructure tags defined in the struct type
// can indicate which fields the values are mapped to.
type Person struct {
Name string
Age int
Other map[string]interface{} `mapstructure:",remain"`
}
input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"email": "mitchell@example.com",
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91, Other:map[string]interface {}{"email":"mitchell@example.com"}}
}
func ExampleDecode_omitempty() {
// Add omitempty annotation to avoid map keys for empty values
type Family struct {
LastName string
}
type Location struct {
City string
}
type Person struct {
*Family `mapstructure:",omitempty"`
*Location `mapstructure:",omitempty"`
Age int
FirstName string
}
result := &map[string]interface{}{}
input := Person{FirstName: "Somebody"}
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%+v", result)
// Output:
// &map[Age:0 FirstName:Somebody]
}

View File

@ -0,0 +1,58 @@
package mapstructure
import (
"reflect"
"testing"
)
func TestDecode_Ptr(t *testing.T) {
t.Parallel()
type G struct {
Id int
Name string
}
type X struct {
Id int
Name int
}
type AG struct {
List []*G
}
type AX struct {
List []*X
}
g2 := &AG{
List: []*G{
{
Id: 11,
Name: "gg",
},
},
}
x2 := AX{}
// 报错但还是会转换成功,转换后值为目标类型的 0 值
err := Decode(g2, &x2)
res := AX{
List: []*X{
{
Id: 11,
Name: 0, // 这个类型的 0 值
},
},
}
if err == nil {
t.Errorf("Decode_Ptr err should not be 'nil': %#v", err)
}
if !reflect.DeepEqual(res, x2) {
t.Errorf("result should be %#v: got %#v", res, x2)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
package mapstructure
import "time"
// DecodeWithTime 支持时间转字符串
// 支持
// 1. *Time.time 转 string/*string
// 2. *Time.time 转 uint/uint32/uint64/int/int32/int64支持带指针
// 不能用 Time.time 转它会在上层认为是一个结构体数据而直接转成map再到hook方法
func DecodeWithTime(input, output interface{}, layout string) error {
if layout == "" {
layout = time.DateTime
}
config := &DecoderConfig{
Metadata: nil,
Result: output,
DecodeHook: ComposeDecodeHookFunc(TimeToStringHook(layout), TimeToUnixIntHook()),
}
decoder, err := NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(input)
}

View File

@ -0,0 +1,101 @@
package mapstructure
import (
"reflect"
"time"
)
// TimeToStringHook 时间转字符串
// 支持 *Time.time 转 string/*string
// 不能用 Time.time 转它会在上层认为是一个结构体数据而直接转成map再到hook方法
func TimeToStringHook(layout string) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
// 判断目标类型是否为字符串
var strType string
var isStrPointer *bool // 要转换的目标类型是否为指针字符串
if t == reflect.TypeOf(strType) {
isStrPointer = new(bool)
} else if t == reflect.TypeOf(&strType) {
isStrPointer = new(bool)
*isStrPointer = true
}
if isStrPointer == nil {
return data, nil
}
// 判断类型是否为时间
timeType := time.Time{}
if f != reflect.TypeOf(timeType) && f != reflect.TypeOf(&timeType) {
return data, nil
}
// 将时间转换为字符串
var output string
switch v := data.(type) {
case *time.Time:
output = v.Format(layout)
case time.Time:
output = v.Format(layout)
default:
return data, nil
}
if *isStrPointer {
return &output, nil
}
return output, nil
}
}
// TimeToUnixIntHook 时间转时间戳
// 支持 *Time.time 转 uint/uint32/uint64/int/int32/int64支持带指针
// 不能用 Time.time 转它会在上层认为是一个结构体数据而直接转成map再到hook方法
func TimeToUnixIntHook() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
tkd := t.Kind()
if tkd != reflect.Int && tkd != reflect.Int32 && tkd != reflect.Int64 &&
tkd != reflect.Uint && tkd != reflect.Uint32 && tkd != reflect.Uint64 {
return data, nil
}
// 判断类型是否为时间
timeType := time.Time{}
if f != reflect.TypeOf(timeType) && f != reflect.TypeOf(&timeType) {
return data, nil
}
// 将时间转换为字符串
var output int64
switch v := data.(type) {
case *time.Time:
output = v.Unix()
case time.Time:
output = v.Unix()
default:
return data, nil
}
switch tkd {
case reflect.Int:
return int(output), nil
case reflect.Int32:
return int32(output), nil
case reflect.Int64:
return output, nil
case reflect.Uint:
return uint(output), nil
case reflect.Uint32:
return uint32(output), nil
case reflect.Uint64:
return uint64(output), nil
default:
return data, nil
}
}
}

View File

@ -0,0 +1,274 @@
package mapstructure
import (
"testing"
"time"
)
func Test_TimeToStringHook(t *testing.T) {
type Input struct {
Time time.Time
Id int
}
type InputTPointer struct {
Time *time.Time
Id int
}
type Output struct {
Time string
Id int
}
type OutputTPointer struct {
Time *string
Id int
}
now := time.Now()
target := now.Format("2006-01-02 15:04:05")
idValue := 1
tests := []struct {
input any
output any
name string
layout string
}{
{
name: "测试Time.time转string",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output{},
},
{
name: "测试*Time.time转*string",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: OutputTPointer{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
decoder, err := NewDecoder(&DecoderConfig{
DecodeHook: TimeToStringHook(tt.layout),
Result: &tt.output,
})
if err != nil {
t.Errorf("NewDecoder() err = %vwant nil", err)
}
if i, isOk := tt.input.(Input); isOk {
err = decoder.Decode(i)
}
if i, isOk := tt.input.(InputTPointer); isOk {
err = decoder.Decode(&i)
}
if err != nil {
t.Errorf("Decode err = %vwant nil", err)
}
//验证测试值
if output, isOk := tt.output.(OutputTPointer); isOk {
if *output.Time != target {
t.Errorf("Decode output time = %vwant %v", *output.Time, target)
}
if output.Id != idValue {
t.Errorf("Decode output id = %vwant %v", output.Id, idValue)
}
}
if output, isOk := tt.output.(Output); isOk {
if output.Time != target {
t.Errorf("Decode output time = %vwant %v", output.Time, target)
}
if output.Id != idValue {
t.Errorf("Decode output id = %vwant %v", output.Id, idValue)
}
}
})
}
}
func Test_TimeToUnixIntHook(t *testing.T) {
type InputTPointer struct {
Time *time.Time
Id int
}
type Output[T int | *int | int32 | *int32 | int64 | *int64 | uint | *uint] struct {
Time T
Id int
}
type test struct {
input any
output any
name string
layout string
}
now := time.Now()
target := now.Unix()
idValue := 1
tests := []test{
{
name: "测试Time.time转int",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[int]{},
},
{
name: "测试Time.time转*int",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[*int]{},
},
{
name: "测试Time.time转int32",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[int32]{},
},
{
name: "测试Time.time转*int32",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[*int32]{},
},
{
name: "测试Time.time转int64",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[int64]{},
},
{
name: "测试Time.time转*int64",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[*int64]{},
},
{
name: "测试Time.time转uint",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[uint]{},
},
{
name: "测试Time.time转*uint",
layout: "2006-01-02 15:04:05",
input: InputTPointer{
Time: &now,
Id: idValue,
},
output: Output[*uint]{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
decoder, err := NewDecoder(&DecoderConfig{
DecodeHook: TimeToUnixIntHook(),
Result: &tt.output,
})
if err != nil {
t.Errorf("NewDecoder() err = %vwant nil", err)
}
if i, isOk := tt.input.(InputTPointer); isOk {
err = decoder.Decode(i)
}
if i, isOk := tt.input.(InputTPointer); isOk {
err = decoder.Decode(&i)
}
if err != nil {
t.Errorf("Decode err = %vwant nil", err)
}
//验证测试值
switch v := tt.output.(type) {
case Output[int]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*int]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[int32]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*int32]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[int64]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*int64]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[uint]:
if int64(v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
case Output[*uint]:
if int64(*v.Time) != target {
t.Errorf("Decode output time = %vwant %v", v.Time, target)
}
if v.Id != idValue {
t.Errorf("Decode output id = %vwant %v", v.Id, idValue)
}
}
})
}
}

View File

@ -30,7 +30,7 @@ func Bootstrap(conf *config.Config) (err error) {
if err != nil {
return
}
db.GetDb().ShowSQL(true)
//注册redis服务
err = redis.Pr.Register(redis.SingletonMain, conf.Redis)
if err != nil {

36
go.mod
View File

@ -8,12 +8,16 @@ require (
github.com/Shopify/sarama v1.19.0
github.com/ahmetb/go-linq/v3 v3.2.0
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/bwmarrin/snowflake v0.3.0
github.com/duke-git/lancet/v2 v2.3.3
github.com/forgoer/openssl v1.6.0
github.com/gin-gonic/gin v1.7.7
github.com/go-kratos/kratos/v2 v2.8.2
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.4
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
github.com/nats-io/nats.go v1.9.1
github.com/openzipkin/zipkin-go v0.2.2
@ -27,8 +31,9 @@ require (
github.com/swaggo/swag v1.7.9
github.com/tjfoc/gmsm v1.4.1
github.com/valyala/fasthttp v1.31.0
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.30.0
github.com/xuri/excelize/v2 v2.9.0
google.golang.org/grpc v1.61.1
google.golang.org/protobuf v1.33.0
gopkg.in/go-playground/validator.v9 v9.31.0
xorm.io/builder v0.3.9
xorm.io/xorm v1.2.5
@ -57,17 +62,19 @@ require (
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-kratos/aegis v0.2.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/form/v4 v4.2.0 // indirect
github.com/go-playground/validator/v10 v10.9.0 // indirect
github.com/go-redis/redis/v8 v8.11.4 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/hetiansu5/accesslog v1.0.0 // indirect
github.com/hetiansu5/cores v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
@ -82,6 +89,7 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nats-io/jwt v0.3.2 // indirect
github.com/nats-io/nkeys v0.1.3 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
@ -90,6 +98,8 @@ require (
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.4 // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
@ -99,20 +109,24 @@ require (
github.com/ugorji/go/codec v1.2.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.1.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
stathat.com/c/consistent v1.0.0 // indirect
xorm.io/core v0.7.3 // indirect
)

94
go.sum
View File

@ -110,6 +110,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -146,6 +148,8 @@ github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/duke-git/lancet/v2 v2.3.3 h1:OhqzNzkbJBS9ZlWLo/C7g+WSAOAAyNj7p9CAiEHurUc=
github.com/duke-git/lancet/v2 v2.3.3/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
@ -173,8 +177,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -195,6 +200,10 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ=
github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI=
github.com/go-kratos/kratos/v2 v2.8.2 h1:EsEA7AmPQ2YQQ0FZrDWO2HgBNqeWM8z/mWKzS5UkQaQ=
github.com/go-kratos/kratos/v2 v2.8.2/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
@ -213,6 +222,8 @@ github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyr
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
@ -280,8 +291,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
@ -300,8 +311,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -321,8 +332,9 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -330,8 +342,9 @@ github.com/gopherjs/gopherjs v0.0.0-20211111143520-d0d5ecc1a356 h1:d3wWSjdOuGrMH
github.com/gopherjs/gopherjs v0.0.0-20211111143520-d0d5ecc1a356/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@ -455,8 +468,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -522,6 +536,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5 h1:r0wwT7PayEjvEHzWXwr1ROi/JSqzujM4w+1L5ikThzQ=
@ -627,6 +643,11 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhD
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
@ -635,8 +656,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
@ -690,8 +712,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/gin-swagger v1.3.3 h1:XHyYmeNVFG5PbyWHG4jXtxOm2P4kiZapDCWsyDDiQ/I=
@ -731,6 +753,12 @@ github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -796,8 +824,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -808,8 +836,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -834,8 +866,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -888,8 +920,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -914,8 +946,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -990,8 +1022,8 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1003,8 +1035,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1074,8 +1106,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1156,8 +1188,8 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@ -1183,8 +1215,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1198,8 +1230,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=