mirror of
https://gitee.com/johng/gf
synced 2026-06-08 18:47:45 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da2ec21571 | |||
| ebb8d8a2f7 | |||
| 9706a9c768 | |||
| cee67a8d4e | |||
| 87650557fd | |||
| d3bf52f12f | |||
| 6b13a4849b | |||
| 8e380c0d9d | |||
| 0caf4bfcec | |||
| 9c3b978b50 | |||
| ab689a7792 | |||
| 846646d92d | |||
| 2eb2b89432 | |||
| 43441a8218 | |||
| 561a541fa1 | |||
| ffe9ecc141 | |||
| 77f7884604 | |||
| 0a203d1e22 | |||
| f4f4550483 | |||
| e87226a092 | |||
| 391a3ec9bd |
@ -2,25 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := gi18n.New()
|
||||
t.SetPath("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/i18n/gi18n/i18n")
|
||||
t.SetLanguage("en")
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`))
|
||||
|
||||
t.SetLanguage("ja")
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`))
|
||||
|
||||
t.SetLanguage("ru")
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`))
|
||||
|
||||
fmt.Println(t.Translate(`hello`, "zh-CN"))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`, "zh-CN"))
|
||||
var (
|
||||
orderId = 865271654
|
||||
orderAmount = 99.8
|
||||
)
|
||||
fmt.Println(g.I18n().Tfl(`en`, `{#OrderPaid}`, orderId, orderAmount))
|
||||
fmt.Println(g.I18n().Tfl(`zh-CN`, `{#OrderPaid}`, orderId, orderAmount))
|
||||
}
|
||||
|
||||
@ -1,3 +1 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f."
|
||||
@ -1,2 +1 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。"
|
||||
@ -3,15 +3,21 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
type GetById struct {
|
||||
Id *g.Var `p:"id" v:"required|integer#id不能为空|id必须为整数"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.SetIndexFolder(true)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
glog.Println(r.Header)
|
||||
r.Response.Write("hello world")
|
||||
var idInfo *GetById
|
||||
if err := r.Parse(&idInfo); err != nil {
|
||||
r.Response.Write(err)
|
||||
}
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
s.SetPort(8999)
|
||||
s.Run()
|
||||
|
||||
@ -11,7 +11,7 @@ func main() {
|
||||
fmt.Println(1)
|
||||
gutil.Throw("error")
|
||||
fmt.Println(2)
|
||||
}, func(err interface{}) {
|
||||
}, func(err error) {
|
||||
fmt.Println(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -128,6 +128,7 @@ type DB interface {
|
||||
GetDryRun() bool
|
||||
SetLogger(logger *glog.Logger)
|
||||
GetLogger() *glog.Logger
|
||||
GetConfig() *ConfigNode
|
||||
SetMaxIdleConnCount(n int)
|
||||
SetMaxOpenConnCount(n int)
|
||||
SetMaxConnLifetime(d time.Duration)
|
||||
@ -171,6 +172,7 @@ type Core struct {
|
||||
dryrun *gtype.Bool // Dry run.
|
||||
prefix string // Table prefix.
|
||||
logger *glog.Logger // Logger.
|
||||
config *ConfigNode // Current config node.
|
||||
maxIdleConnCount int // Max idle connection count.
|
||||
maxOpenConnCount int // Max open connection count.
|
||||
maxConnLifetime time.Duration // Max TTL for a connection.
|
||||
@ -304,6 +306,7 @@ func New(group ...string) (db DB, err error) {
|
||||
dryrun: gtype.NewBool(),
|
||||
logger: glog.New(),
|
||||
prefix: node.Prefix,
|
||||
config: node,
|
||||
maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
|
||||
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
@ -455,7 +456,7 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
for k, _ := range dataMap {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
@ -602,7 +603,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
for _, k := range keys {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
@ -829,3 +830,22 @@ func (c *Core) HasTable(name string) (bool, error) {
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFiledName(fieldName string) bool {
|
||||
if fieldName == "" {
|
||||
return false
|
||||
}
|
||||
if config := c.DB.GetConfig(); config.CreatedAt != "" {
|
||||
if utils.EqualFoldWithoutChars(fieldName, config.CreatedAt) {
|
||||
return true
|
||||
}
|
||||
return gstr.InArray(append([]string{config.CreatedAt}, createdFiledNames...), fieldName)
|
||||
}
|
||||
for _, v := range createdFiledNames {
|
||||
if utils.EqualFoldWithoutChars(fieldName, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -39,6 +39,9 @@ type ConfigNode struct {
|
||||
DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
|
||||
Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
|
||||
Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
|
||||
CreatedAt string // (Optional) The filed name of table for automatic-filled created datetime.
|
||||
UpdatedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
|
||||
DeletedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
|
||||
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
|
||||
MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool.
|
||||
MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool.
|
||||
@ -212,3 +215,8 @@ func (c *Core) SetSchema(schema string) {
|
||||
func (c *Core) GetSchema() string {
|
||||
return c.schema.Val()
|
||||
}
|
||||
|
||||
// GetConfig returns the current used node configuration.
|
||||
func (c *Core) GetConfig() *ConfigNode {
|
||||
return c.config
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
}
|
||||
}()
|
||||
var (
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
)
|
||||
// Soft deleting.
|
||||
|
||||
@ -149,9 +149,9 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
}
|
||||
var (
|
||||
nowString = gtime.Now().String()
|
||||
fieldNameCreate = m.getSoftFieldNameCreate()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdate()
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
)
|
||||
// Batch operation.
|
||||
if list, ok := m.data.(List); ok {
|
||||
|
||||
@ -15,10 +15,10 @@ import (
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
gSOFT_FIELD_NAME_CREATE = "create_at"
|
||||
gSOFT_FIELD_NAME_UPDATE = "update_at"
|
||||
gSOFT_FIELD_NAME_DELETE = "delete_at"
|
||||
var (
|
||||
createdFiledNames = []string{"created_at", "create_at"} // Default filed names of table for automatic-filled created datetime.
|
||||
updatedFiledNames = []string{"updated_at", "update_at"} // Default filed names of table for automatic-filled updated datetime.
|
||||
deletedFiledNames = []string{"deleted_at", "delete_at"} // Default filed names of table for automatic-filled deleted datetime.
|
||||
)
|
||||
|
||||
// Unscoped disables the auto-update time feature for insert, update and delete options.
|
||||
@ -31,49 +31,66 @@ func (m *Model) Unscoped() *Model {
|
||||
// getSoftFieldNameCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameCreate(table ...string) string {
|
||||
func (m *Model) getSoftFieldNameCreated(table ...string) string {
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
}
|
||||
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_CREATE)
|
||||
config := m.db.GetConfig()
|
||||
if config.CreatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, append([]string{config.CreatedAt}, createdFiledNames...))
|
||||
}
|
||||
return m.getSoftFieldName(tableName, createdFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
|
||||
// If there's no field name for storing updating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameUpdate(table ...string) (field string) {
|
||||
func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) {
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
}
|
||||
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_UPDATE)
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, append([]string{config.UpdatedAt}, updatedFiledNames...))
|
||||
}
|
||||
return m.getSoftFieldName(tableName, updatedFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
|
||||
// If there's no field name for storing deleting time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameDelete(table ...string) (field string) {
|
||||
func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) {
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
}
|
||||
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_DELETE)
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, append([]string{config.DeletedAt}, deletedFiledNames...))
|
||||
}
|
||||
return m.getSoftFieldName(tableName, deletedFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldName retrieves and returns the field name of the table for possible key.
|
||||
func (m *Model) getSoftFieldName(table string, key string) (field string) {
|
||||
func (m *Model) getSoftFieldName(table string, keys []string) (field string) {
|
||||
fieldsMap, _ := m.db.TableFields(table)
|
||||
if len(fieldsMap) > 0 {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), key,
|
||||
)
|
||||
for _, key := range keys {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), key,
|
||||
)
|
||||
if field != "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -110,7 +127,7 @@ func (m *Model) getConditionForSoftDeleting() string {
|
||||
return conditionArray.Join(" AND ")
|
||||
}
|
||||
// Only one table.
|
||||
if fieldName := m.getSoftFieldNameDelete(); fieldName != "" {
|
||||
if fieldName := m.getSoftFieldNameDeleted(); fieldName != "" {
|
||||
return fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(fieldName))
|
||||
}
|
||||
return ""
|
||||
@ -129,7 +146,7 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
|
||||
} else {
|
||||
table = array2[0]
|
||||
}
|
||||
field = m.getSoftFieldNameDelete(table)
|
||||
field = m.getSoftFieldNameDeleted(table)
|
||||
if field == "" {
|
||||
return ""
|
||||
}
|
||||
@ -42,9 +42,9 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
var (
|
||||
updateData = m.data
|
||||
fieldNameCreate = m.getSoftFieldNameCreate()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdate()
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
)
|
||||
// Automatically update the record updating time.
|
||||
|
||||
@ -204,9 +204,9 @@ CREATE TABLE %s (
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
model := db.Table(table1)
|
||||
gtest.Assert(model.getSoftFieldNameCreate(table2), "createat")
|
||||
gtest.Assert(model.getSoftFieldNameUpdate(table2), "updateat")
|
||||
gtest.Assert(model.getSoftFieldNameDelete(table2), "deleteat")
|
||||
gtest.Assert(model.getSoftFieldNameCreated(table2), "createat")
|
||||
gtest.Assert(model.getSoftFieldNameUpdated(table2), "updateat")
|
||||
gtest.Assert(model.getSoftFieldNameDeleted(table2), "deleteat")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -69,3 +69,31 @@ func Test_Load_New_CustomStruct(t *testing.T) {
|
||||
t.Assert(s == `{"Id":1,"Name":"john"}` || s == `{"Name":"john","Id":1}`, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Load_New_HierarchicalStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Me struct {
|
||||
Name string `json:"name"`
|
||||
Score int `json:"score"`
|
||||
Children []Me `json:"children"`
|
||||
}
|
||||
me := Me{
|
||||
Name: "john",
|
||||
Score: 100,
|
||||
Children: []Me{
|
||||
{
|
||||
Name: "Bean",
|
||||
Score: 99,
|
||||
},
|
||||
{
|
||||
Name: "Sam",
|
||||
Score: 98,
|
||||
},
|
||||
},
|
||||
}
|
||||
j := gjson.New(me)
|
||||
t.Assert(j.Remove("children.0.score"), nil)
|
||||
t.Assert(j.Remove("children.1.score"), nil)
|
||||
t.Assert(j.MustToJsonString(), `{"children":[{"children":null,"name":"Bean"},{"children":null,"name":"Sam"}],"name":"john","score":100}`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -136,7 +136,7 @@ func Current(err error) error {
|
||||
if e, ok := err.(ApiLevel); ok {
|
||||
return e.Current()
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Next returns the next level error.
|
||||
|
||||
@ -39,8 +39,15 @@ func Throw(exception interface{}) {
|
||||
gutil.Throw(exception)
|
||||
}
|
||||
|
||||
// TryCatch does the try...catch... mechanism.
|
||||
func TryCatch(try func(), catch ...func(exception interface{})) {
|
||||
// Try implements try... logistics using internal panic...recover.
|
||||
// It returns error if any exception occurs, or else it returns nil.
|
||||
func Try(try func()) (err error) {
|
||||
return gutil.Try(try)
|
||||
}
|
||||
|
||||
// TryCatch implements try...catch... logistics using internal panic...recover.
|
||||
// It automatically calls function <catch> if any exception occurs ans passes the exception as an error.
|
||||
func TryCatch(try func(), catch ...func(exception error)) {
|
||||
gutil.TryCatch(try, catch...)
|
||||
}
|
||||
|
||||
|
||||
@ -32,14 +32,14 @@ func T(content string, language ...string) string {
|
||||
return defaultManager.T(content, language...)
|
||||
}
|
||||
|
||||
// TF is alias of TranslateFormat for convenience.
|
||||
func TF(format string, values ...interface{}) string {
|
||||
// Tf is alias of TranslateFormat for convenience.
|
||||
func Tf(format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TFL is alias of TranslateFormatLang for convenience.
|
||||
func TFL(format string, language string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(format, language, values...)
|
||||
// Tfl is alias of TranslateFormatLang for convenience.
|
||||
func Tfl(language string, format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(language, format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
@ -52,7 +52,7 @@ func TranslateFormat(format string, values ...interface{}) string {
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func TranslateFormatLang(format string, language string, values ...interface{}) string {
|
||||
func TranslateFormatLang(language string, format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
|
||||
@ -124,14 +124,14 @@ func (m *Manager) T(content string, language ...string) string {
|
||||
return m.Translate(content, language...)
|
||||
}
|
||||
|
||||
// TF is alias of TranslateFormat for convenience.
|
||||
func (m *Manager) TF(format string, values ...interface{}) string {
|
||||
// Tf is alias of TranslateFormat for convenience.
|
||||
func (m *Manager) Tf(format string, values ...interface{}) string {
|
||||
return m.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TFL is alias of TranslateFormatLang for convenience.
|
||||
func (m *Manager) TFL(format string, language string, values ...interface{}) string {
|
||||
return m.TranslateFormatLang(format, language, values...)
|
||||
// Tfl is alias of TranslateFormatLang for convenience.
|
||||
func (m *Manager) Tfl(language string, format string, values ...interface{}) string {
|
||||
return m.TranslateFormatLang(language, format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
@ -144,7 +144,7 @@ func (m *Manager) TranslateFormat(format string, values ...interface{}) string {
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func (m *Manager) TranslateFormatLang(format string, language string, values ...interface{}) string {
|
||||
func (m *Manager) TranslateFormatLang(language string, format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format, language), values...)
|
||||
}
|
||||
|
||||
|
||||
@ -74,24 +74,24 @@ func Test_Basic(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_TranslateFormat(t *testing.T) {
|
||||
// TF
|
||||
// Tf
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.TF("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
|
||||
t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.TF("{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
})
|
||||
// TFL
|
||||
// Tfl
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
t.Assert(i18n.TFL("{#hello}{#world} %d", "ja", 2020), "こんにちは世界 2020")
|
||||
t.Assert(i18n.TFL("{#hello}{#world} %d", "zh-CN", 2020), "你好世界 2020")
|
||||
t.Assert(i18n.Tfl("ja", "{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
t.Assert(i18n.Tfl("zh-CN", "{#hello}{#world} %d", 2020), "你好世界 2020")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -69,12 +69,21 @@ func IsEmpty(value interface{}) bool {
|
||||
default:
|
||||
// Common interfaces checks.
|
||||
if f, ok := value.(apiString); ok {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
return f.String() == ""
|
||||
}
|
||||
if f, ok := value.(apiInterfaces); ok {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
return len(f.Interfaces()) == 0
|
||||
}
|
||||
if f, ok := value.(apiMapStrAny); ok {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
return len(f.MapStrAny()) == 0
|
||||
}
|
||||
// Finally using reflect.
|
||||
|
||||
@ -22,6 +22,21 @@ import (
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
|
||||
// If <pointer> points to an invalid address, for example a nil variable,
|
||||
// it here creates an empty struct using reflect feature.
|
||||
var (
|
||||
tempValue reflect.Value
|
||||
pointerValue = reflect.ValueOf(pointer)
|
||||
)
|
||||
for pointerValue.Kind() == reflect.Ptr {
|
||||
tempValue = pointerValue.Elem()
|
||||
if !tempValue.IsValid() {
|
||||
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
|
||||
break
|
||||
} else {
|
||||
pointerValue = tempValue
|
||||
}
|
||||
}
|
||||
var (
|
||||
fields []*structs.Field
|
||||
fieldMap = make(map[string]*Field)
|
||||
@ -59,8 +74,10 @@ func MapField(pointer interface{}, priority []string, recursive bool) map[string
|
||||
}
|
||||
}
|
||||
if recursive {
|
||||
rv := reflect.ValueOf(field.Value())
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(field.Value())
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
|
||||
@ -27,6 +27,21 @@ func TagFields(pointer interface{}, priority []string, recursive bool) []*Field
|
||||
// tag internally.
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field {
|
||||
// If <pointer> points to an invalid address, for example a nil variable,
|
||||
// it here creates an empty struct using reflect feature.
|
||||
var (
|
||||
tempValue reflect.Value
|
||||
pointerValue = reflect.ValueOf(pointer)
|
||||
)
|
||||
for pointerValue.Kind() == reflect.Ptr {
|
||||
tempValue = pointerValue.Elem()
|
||||
if !tempValue.IsValid() {
|
||||
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
|
||||
break
|
||||
} else {
|
||||
pointerValue = tempValue
|
||||
}
|
||||
}
|
||||
var fields []*structs.Field
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
|
||||
@ -71,3 +71,20 @@ func Test_Basic(t *testing.T) {
|
||||
t.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructOfNilPointer(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
|
||||
param = BuildParams(data[0])
|
||||
}
|
||||
}
|
||||
req := (*http.Request)(nil)
|
||||
var req *http.Request
|
||||
if strings.Contains(param, "@file:") {
|
||||
// File uploading request.
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
@ -8,12 +8,17 @@ package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/encoding/gurl"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
fileUploadingKey = "@file:"
|
||||
)
|
||||
|
||||
// BuildParams builds the request string for the http client. The <params> can be type of:
|
||||
// string/[]byte/map/struct/*struct.
|
||||
//
|
||||
@ -38,13 +43,22 @@ func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr strin
|
||||
if len(noUrlEncode) == 1 {
|
||||
urlEncode = !noUrlEncode[0]
|
||||
}
|
||||
// If there's file uploading, it ignores the url encoding.
|
||||
if urlEncode {
|
||||
for k, v := range m {
|
||||
if gstr.Contains(k, fileUploadingKey) || gstr.Contains(gconv.String(v), fileUploadingKey) {
|
||||
urlEncode = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
s := ""
|
||||
for k, v := range m {
|
||||
if len(encodedParamStr) > 0 {
|
||||
encodedParamStr += "&"
|
||||
}
|
||||
s = gconv.String(v)
|
||||
if urlEncode && len(s) > 6 && strings.Compare(s[0:6], "@file:") != 0 {
|
||||
if urlEncode && len(s) > 6 && strings.Compare(s[0:6], fileUploadingKey) != 0 {
|
||||
s = gurl.Encode(s)
|
||||
}
|
||||
encodedParamStr += k + "=" + s
|
||||
|
||||
@ -120,7 +120,7 @@ func (m *Middleware) Next() {
|
||||
// There should be a "Next" function to be called in the middleware in order to manage the workflow.
|
||||
loop = false
|
||||
}
|
||||
}, func(exception interface{}) {
|
||||
}, func(exception error) {
|
||||
if e, ok := exception.(gerror.ApiStack); ok {
|
||||
// It's already an error that has stack info.
|
||||
m.request.error = e.(error)
|
||||
|
||||
@ -25,6 +25,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
parseTypeRequest = 0
|
||||
parseTypeQuery = 1
|
||||
parseTypeForm = 2
|
||||
)
|
||||
|
||||
var (
|
||||
// xmlHeaderBytes is the most common XML format header.
|
||||
xmlHeaderBytes = []byte("<?xml")
|
||||
@ -37,11 +43,26 @@ var (
|
||||
// The parameter <pointer> can be type of: *struct/**struct/*[]struct/*[]*struct.
|
||||
//
|
||||
// It supports single and multiple struct convertion:
|
||||
// 1. Single struct, post content like: {"id":1, "name":"john"}
|
||||
// 1. Single struct, post content like: {"id":1, "name":"john"} or ?id=1&name=john
|
||||
// 2. Multiple struct, post content like: [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
|
||||
//
|
||||
// TODO: Improve the performance by reducing duplicated reflect usage on the same variable across packages.
|
||||
func (r *Request) Parse(pointer interface{}) error {
|
||||
return r.doParse(pointer, parseTypeRequest)
|
||||
}
|
||||
|
||||
// ParseQuery performs like function Parse, but only parses the query parameters.
|
||||
func (r *Request) ParseQuery(pointer interface{}) error {
|
||||
return r.doParse(pointer, parseTypeQuery)
|
||||
}
|
||||
|
||||
// ParseForm performs like function Parse, but only parses the form parameters or the body content.
|
||||
func (r *Request) ParseForm(pointer interface{}) error {
|
||||
return r.doParse(pointer, parseTypeForm)
|
||||
}
|
||||
|
||||
// doParse parses the request data to struct/structs according to request type.
|
||||
func (r *Request) doParse(pointer interface{}, requestType int) error {
|
||||
var (
|
||||
reflectVal1 = reflect.ValueOf(pointer)
|
||||
reflectKind1 = reflectVal1.Kind()
|
||||
@ -58,18 +79,31 @@ func (r *Request) Parse(pointer interface{}) error {
|
||||
)
|
||||
switch reflectKind2 {
|
||||
// Single struct, post content like:
|
||||
// {"id":1, "name":"john"}
|
||||
// 1. {"id":1, "name":"john"}
|
||||
// 2. ?id=1&name=john
|
||||
case reflect.Ptr, reflect.Struct:
|
||||
// Conversion.
|
||||
if err := r.GetStruct(pointer); err != nil {
|
||||
return err
|
||||
// Converting.
|
||||
switch requestType {
|
||||
case parseTypeQuery:
|
||||
if err := r.GetQueryStruct(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
case parseTypeForm:
|
||||
if err := r.GetFormStruct(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := r.GetStruct(pointer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Validation.
|
||||
if err := gvalid.CheckStruct(pointer, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Multiple struct, post content like:
|
||||
// Multiple struct, it only supports JSON type post content like:
|
||||
// [{"id":1, "name":"john"}, {"id":, "name":"smith"}]
|
||||
case reflect.Array, reflect.Slice:
|
||||
// If struct slice conversion, it might post JSON/XML content,
|
||||
@ -371,11 +405,14 @@ func (r *Request) parseForm() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.formMap == nil {
|
||||
}
|
||||
// It parses the request body without checking the Content-Type.
|
||||
if r.formMap == nil {
|
||||
if r.Method != "GET" {
|
||||
r.parseBody()
|
||||
if len(r.bodyMap) > 0 {
|
||||
r.formMap = r.bodyMap
|
||||
}
|
||||
}
|
||||
if len(r.bodyMap) > 0 {
|
||||
r.formMap = r.bodyMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +193,7 @@ func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]strin
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
return gconv.Struct(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetFormToStruct is alias of GetFormStruct. See GetFormStruct.
|
||||
|
||||
@ -204,7 +204,7 @@ func (r *Request) GetPostMapStrVar(kvMap ...map[string]interface{}) map[string]*
|
||||
//
|
||||
// Deprecated.
|
||||
func (r *Request) GetPostStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(r.GetPostMap(), pointer, mapping...)
|
||||
return gconv.Struct(r.GetPostMap(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetPostToStruct is alias of GetQueryStruct. See GetPostStruct.
|
||||
|
||||
@ -34,7 +34,9 @@ func (r *Request) GetQuery(key string, def ...interface{}) interface{} {
|
||||
return v
|
||||
}
|
||||
}
|
||||
r.parseBody()
|
||||
if r.Method == "GET" {
|
||||
r.parseBody()
|
||||
}
|
||||
if len(r.bodyMap) > 0 {
|
||||
if v, ok := r.bodyMap[key]; ok {
|
||||
return v
|
||||
@ -118,7 +120,9 @@ func (r *Request) GetQueryInterfaces(key string, def ...interface{}) []interface
|
||||
// in order of priority: query > body.
|
||||
func (r *Request) GetQueryMap(kvMap ...map[string]interface{}) map[string]interface{} {
|
||||
r.parseQuery()
|
||||
r.parseBody()
|
||||
if r.Method == "GET" {
|
||||
r.parseBody()
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if len(kvMap) > 0 && kvMap[0] != nil {
|
||||
if len(r.queryMap) == 0 && len(r.bodyMap) == 0 {
|
||||
@ -197,7 +201,7 @@ func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]stri
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
return gconv.Struct(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetQueryToStruct is alias of GetQueryStruct. See GetQueryStruct.
|
||||
|
||||
@ -271,7 +271,7 @@ func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]st
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
}
|
||||
return gconv.StructDeep(m, pointer, mapping...)
|
||||
return gconv.Struct(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetRequestToStruct is alias of GetRequestStruct. See GetRequestStruct.
|
||||
|
||||
@ -295,6 +295,8 @@ func Test_Client_Param_Containing_Special_Char(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// It posts data along with file uploading.
|
||||
// It does not url-encodes the parameters.
|
||||
func Test_Client_File_And_Param(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
@ -308,7 +310,7 @@ func Test_Client_File_And_Param(t *testing.T) {
|
||||
_, err = file.Save(tmpPath)
|
||||
gtest.Assert(err, nil)
|
||||
r.Response.Write(
|
||||
r.Get("key"),
|
||||
r.Get("json"),
|
||||
gfile.GetContents(gfile.Join(tmpPath, gfile.Basename(file.Filename))),
|
||||
)
|
||||
})
|
||||
@ -320,12 +322,13 @@ func Test_Client_File_And_Param(t *testing.T) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := gdebug.TestDataPath("upload", "file1.txt")
|
||||
data := g.Map{
|
||||
"file": "@file:" + path,
|
||||
"json": `{"uuid": "luijquiopm", "isRelative": false, "fileName": "test111.xls"}`,
|
||||
}
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
filePath := gdebug.TestDataPath("upload", "file1.txt")
|
||||
t.Assert(
|
||||
c.PostContent("/", "key=1&file=@file:"+filePath),
|
||||
"1"+gfile.GetContents(filePath),
|
||||
)
|
||||
t.Assert(c.PostContent("/", data), data["json"].(string)+gfile.GetContents(path))
|
||||
})
|
||||
}
|
||||
|
||||
@ -27,13 +27,11 @@ func Test_Params_Parse(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
var user *User
|
||||
if err := r.Parse(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Map["id"], user.Map["score"])
|
||||
var user *User
|
||||
if err := r.Parse(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Map["id"], user.Map["score"])
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
@ -48,6 +46,250 @@ func Test_Params_Parse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_ParseQuery(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/parse-query", func(r *ghttp.Request) {
|
||||
var user *User
|
||||
if err := r.ParseQuery(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Id, user.Name)
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
t.Assert(c.GetContent("/parse-query"), `0`)
|
||||
t.Assert(c.GetContent("/parse-query?id=1&name=john"), `1john`)
|
||||
t.Assert(c.PostContent("/parse-query"), `0`)
|
||||
t.Assert(c.PostContent("/parse-query", g.Map{
|
||||
"id": 1,
|
||||
"name": "john",
|
||||
}), `0`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_ParseForm(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/parse-form", func(r *ghttp.Request) {
|
||||
var user *User
|
||||
if err := r.ParseForm(&user); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(user.Id, user.Name)
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
t.Assert(c.GetContent("/parse-form"), `0`)
|
||||
t.Assert(c.GetContent("/parse-form", g.Map{
|
||||
"id": 1,
|
||||
"name": "john",
|
||||
}), 0)
|
||||
t.Assert(c.PostContent("/parse-form"), `0`)
|
||||
t.Assert(c.PostContent("/parse-form", g.Map{
|
||||
"id": 1,
|
||||
"name": "john",
|
||||
}), `1john`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_ComplexJsonStruct(t *testing.T) {
|
||||
type ItemEnv struct {
|
||||
Type string
|
||||
Key string
|
||||
Value string
|
||||
Brief string
|
||||
}
|
||||
|
||||
type ItemProbe struct {
|
||||
Type string
|
||||
Port int
|
||||
Path string
|
||||
Brief string
|
||||
Period int
|
||||
InitialDelay int
|
||||
TimeoutSeconds int
|
||||
}
|
||||
|
||||
type ItemKV struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
type ItemPort struct {
|
||||
Port int
|
||||
Type string
|
||||
Alias string
|
||||
Brief string
|
||||
}
|
||||
|
||||
type ItemMount struct {
|
||||
Type string
|
||||
DstPath string
|
||||
Src string
|
||||
SrcPath string
|
||||
Brief string
|
||||
}
|
||||
|
||||
type SaveRequest struct {
|
||||
AppId uint
|
||||
Name string
|
||||
Type string
|
||||
Cluster string
|
||||
Replicas uint
|
||||
ContainerName string
|
||||
ContainerImage string
|
||||
VersionTag string
|
||||
Namespace string
|
||||
Id uint
|
||||
Status uint
|
||||
Metrics string
|
||||
InitImage string
|
||||
CpuRequest uint
|
||||
CpuLimit uint
|
||||
MemRequest uint
|
||||
MemLimit uint
|
||||
MeshEnabled uint
|
||||
ContainerPorts []ItemPort
|
||||
Labels []ItemKV
|
||||
NodeSelector []ItemKV
|
||||
EnvReserve []ItemKV
|
||||
EnvGlobal []ItemEnv
|
||||
EnvContainer []ItemEnv
|
||||
Mounts []ItemMount
|
||||
LivenessProbe ItemProbe
|
||||
ReadinessProbe ItemProbe
|
||||
}
|
||||
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
if m := r.GetMap(); len(m) > 0 {
|
||||
var data *SaveRequest
|
||||
if err := r.Parse(&data); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(data)
|
||||
}
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
content := `
|
||||
{
|
||||
"app_id": 5,
|
||||
"cluster": "test",
|
||||
"container_image": "nginx",
|
||||
"container_name": "test",
|
||||
"container_ports": [
|
||||
{
|
||||
"alias": "别名",
|
||||
"brief": "描述",
|
||||
"port": 80,
|
||||
"type": "tcp"
|
||||
}
|
||||
],
|
||||
"cpu_limit": 100,
|
||||
"cpu_request": 10,
|
||||
"create_at": "2020-10-10 12:00:00",
|
||||
"creator": 1,
|
||||
"env_container": [
|
||||
{
|
||||
"brief": "用户环境变量",
|
||||
"key": "NAME",
|
||||
"type": "string",
|
||||
"value": "john"
|
||||
}
|
||||
],
|
||||
"env_global": [
|
||||
{
|
||||
"brief": "数据数量",
|
||||
"key": "NUMBER",
|
||||
"type": "string",
|
||||
"value": "1"
|
||||
}
|
||||
],
|
||||
"env_reserve": [
|
||||
{
|
||||
"key": "NODE_IP",
|
||||
"value": "status.hostIP"
|
||||
}
|
||||
],
|
||||
"liveness_probe": {
|
||||
"brief": "存活探针",
|
||||
"initial_delay": 10,
|
||||
"path": "",
|
||||
"period": 5,
|
||||
"port": 80,
|
||||
"type": "tcpSocket"
|
||||
},
|
||||
"readiness_probe": {
|
||||
"brief": "就绪探针",
|
||||
"initial_delay": 10,
|
||||
"path": "",
|
||||
"period": 5,
|
||||
"port": 80,
|
||||
"type": "tcpSocket"
|
||||
},
|
||||
"id": 0,
|
||||
"init_image": "",
|
||||
"labels": [
|
||||
{
|
||||
"key": "app",
|
||||
"value": "test"
|
||||
}
|
||||
],
|
||||
"mem_limit": 1000,
|
||||
"mem_request": 100,
|
||||
"mesh_enabled": 0,
|
||||
"metrics": "",
|
||||
"mounts": [],
|
||||
"name": "test",
|
||||
"namespace": "test",
|
||||
"node_selector": [
|
||||
{
|
||||
"key": "group",
|
||||
"value": "app"
|
||||
}
|
||||
],
|
||||
"replicas": 1,
|
||||
"type": "test",
|
||||
"update_at": "2020-10-10 12:00:00",
|
||||
"version_tag": "test"
|
||||
}
|
||||
`
|
||||
t.Assert(client.PostContent("/parse", content), `{"AppId":5,"Name":"test","Type":"test","Cluster":"test","Replicas":1,"ContainerName":"test","ContainerImage":"nginx","VersionTag":"test","Namespace":"test","Id":0,"Status":0,"Metrics":"","InitImage":"","CpuRequest":10,"CpuLimit":100,"MemRequest":100,"MemLimit":1000,"MeshEnabled":0,"ContainerPorts":[{"Port":80,"Type":"tcp","Alias":"别名","Brief":"描述"}],"Labels":[{"Key":"app","Value":"test"}],"NodeSelector":[{"Key":"group","Value":"app"}],"EnvReserve":[{"Key":"NODE_IP","Value":"status.hostIP"}],"EnvGlobal":[{"Type":"string","Key":"NUMBER","Value":"1","Brief":"数据数量"}],"EnvContainer":[{"Type":"string","Key":"NAME","Value":"john","Brief":"用户环境变量"}],"Mounts":[],"LivenessProbe":{"Type":"tcpSocket","Port":80,"Path":"","Brief":"存活探针","Period":5,"InitialDelay":10,"TimeoutSeconds":0},"ReadinessProbe":{"Type":"tcpSocket","Port":80,"Path":"","Brief":"就绪探针","Period":5,"InitialDelay":10,"TimeoutSeconds":0}}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Parse_Attr_Pointer1(t *testing.T) {
|
||||
type User struct {
|
||||
Id *int
|
||||
|
||||
@ -404,7 +404,7 @@ func Test_Params_Basic(t *testing.T) {
|
||||
t.Assert(client.GetContent("/struct", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
t.Assert(client.PostContent("/struct", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
t.Assert(client.PostContent("/struct-with-nil", ``), ``)
|
||||
t.Assert(client.PostContent("/struct-with-base", `id=1&name=john&password1=123&password2=456`), "1john1234561john123456")
|
||||
t.Assert(client.PostContent("/struct-with-base", `id=1&name=john&password1=123&password2=456`), "1john1234561john")
|
||||
})
|
||||
}
|
||||
|
||||
@ -433,10 +433,10 @@ func Test_Params_SupportChars(t *testing.T) {
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/form-value", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetQuery("test-value"))
|
||||
r.Response.Write(r.GetForm("test-value"))
|
||||
})
|
||||
s.BindHandler("/form-array", func(r *ghttp.Request) {
|
||||
r.Response.Write(r.GetQuery("test-array"))
|
||||
r.Response.Write(r.GetForm("test-array"))
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
@ -445,12 +445,10 @@ func Test_Params_SupportChars(t *testing.T) {
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.PostContent("/form-value", "test-value=100"), "100")
|
||||
t.Assert(client.PostContent("/form-array", "test-array[]=1&test-array[]=2"), `["1","2"]`)
|
||||
c := g.Client()
|
||||
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
t.Assert(c.PostContent("/form-value", "test-value=100"), "100")
|
||||
t.Assert(c.PostContent("/form-array", "test-array[]=1&test-array[]=2"), `["1","2"]`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
|
||||
// Time is a wrapper for time.Time for additional features.
|
||||
type Time struct {
|
||||
TimeWrapper
|
||||
wrapper
|
||||
}
|
||||
|
||||
// apiUnixNano is an interface definition commonly for custom time.Time wrapper.
|
||||
@ -28,7 +28,6 @@ func New(param ...interface{}) *Time {
|
||||
if len(param) > 0 {
|
||||
switch r := param[0].(type) {
|
||||
case time.Time:
|
||||
r.Nanosecond()
|
||||
return NewFromTime(r)
|
||||
case *time.Time:
|
||||
return NewFromTime(*r)
|
||||
@ -47,21 +46,21 @@ func New(param ...interface{}) *Time {
|
||||
}
|
||||
}
|
||||
return &Time{
|
||||
TimeWrapper{time.Time{}},
|
||||
wrapper{time.Time{}},
|
||||
}
|
||||
}
|
||||
|
||||
// Now creates and returns a time object of now.
|
||||
func Now() *Time {
|
||||
return &Time{
|
||||
TimeWrapper{time.Now()},
|
||||
wrapper{time.Now()},
|
||||
}
|
||||
}
|
||||
|
||||
// NewFromTime creates and returns a Time object with given time.Time object.
|
||||
func NewFromTime(t time.Time) *Time {
|
||||
return &Time{
|
||||
TimeWrapper{t},
|
||||
wrapper{t},
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +111,7 @@ func NewFromTimeStamp(timestamp int64) *Time {
|
||||
sec = timestamp
|
||||
}
|
||||
return &Time{
|
||||
TimeWrapper{time.Unix(sec, nano)},
|
||||
wrapper{time.Unix(sec, nano)},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,14 +10,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeWrapper is a wrapper for stdlib struct time.Time.
|
||||
// wrapper is a wrapper for stdlib struct time.Time.
|
||||
// It's used for overwriting some functions of time.Time, for example: String.
|
||||
type TimeWrapper struct {
|
||||
type wrapper struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// String overwrites the String function of time.Time.
|
||||
func (t TimeWrapper) String() string {
|
||||
func (t wrapper) String() string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ func Test_String(t *testing.T) {
|
||||
|
||||
t2 := *t1
|
||||
t.Assert(t2.String(), "2006-01-02 15:04:05")
|
||||
t.Assert(fmt.Sprintf("%s", t2), "{2006-01-02 15:04:05}")
|
||||
t.Assert(fmt.Sprintf("{%s}", t2.String()), "{2006-01-02 15:04:05}")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +42,15 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
newTags := StructTagPriority
|
||||
switch len(tags) {
|
||||
case 0:
|
||||
// No need handle.
|
||||
case 1:
|
||||
newTags = append(strings.Split(tags[0], ","), StructTagPriority...)
|
||||
default:
|
||||
newTags = append(tags, StructTagPriority...)
|
||||
}
|
||||
// Assert the common combination of types, and finally it uses reflection.
|
||||
dataMap := make(map[string]interface{})
|
||||
switch r := value.(type) {
|
||||
@ -66,7 +74,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
for k, v := range r {
|
||||
dataMap[String(k)] = v
|
||||
dataMap[String(k)] = doMapConvertForMapOrStructValue(false, v, recursive, newTags...)
|
||||
}
|
||||
case map[interface{}]string:
|
||||
for k, v := range r {
|
||||
@ -109,10 +117,18 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
dataMap[k] = v
|
||||
}
|
||||
case map[string]interface{}:
|
||||
return r
|
||||
if recursive {
|
||||
// A copy of current map.
|
||||
for k, v := range r {
|
||||
dataMap[k] = doMapConvertForMapOrStructValue(false, v, recursive, newTags...)
|
||||
}
|
||||
} else {
|
||||
// It returns the map directly without any changing.
|
||||
return r
|
||||
}
|
||||
case map[int]interface{}:
|
||||
for k, v := range r {
|
||||
dataMap[String(k)] = v
|
||||
dataMap[String(k)] = doMapConvertForMapOrStructValue(false, v, recursive, newTags...)
|
||||
}
|
||||
case map[int]string:
|
||||
for k, v := range r {
|
||||
@ -151,117 +167,189 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
|
||||
dataMap[String(rv.Index(i).Interface())] = nil
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
ks := rv.MapKeys()
|
||||
for _, k := range ks {
|
||||
dataMap[String(k.Interface())] = rv.MapIndex(k).Interface()
|
||||
case reflect.Map, reflect.Struct:
|
||||
convertedValue := doMapConvertForMapOrStructValue(true, value, recursive, newTags...)
|
||||
if m, ok := convertedValue.(map[string]interface{}); ok {
|
||||
return m
|
||||
}
|
||||
case reflect.Struct:
|
||||
// Map converting interface check.
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
}
|
||||
// Using reflect for converting.
|
||||
var (
|
||||
rtField reflect.StructField
|
||||
rvField reflect.Value
|
||||
rt = rv.Type()
|
||||
name = ""
|
||||
tagArray = StructTagPriority
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dataMap
|
||||
}
|
||||
|
||||
func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive bool, tags ...string) interface{} {
|
||||
if isRoot == false && recursive == false {
|
||||
return value
|
||||
}
|
||||
var rv reflect.Value
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
rv = v
|
||||
value = v.Interface()
|
||||
} else {
|
||||
rv = reflect.ValueOf(value)
|
||||
}
|
||||
kind := rv.Kind()
|
||||
// If it is a pointer, we should find its real data type.
|
||||
for kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Map:
|
||||
var (
|
||||
mapKeys = rv.MapKeys()
|
||||
dataMap = make(map[string]interface{})
|
||||
)
|
||||
for _, k := range mapKeys {
|
||||
dataMap[String(k.Interface())] = doMapConvertForMapOrStructValue(
|
||||
false,
|
||||
rv.MapIndex(k).Interface(),
|
||||
recursive,
|
||||
tags...,
|
||||
)
|
||||
switch len(tags) {
|
||||
case 0:
|
||||
// No need handle.
|
||||
case 1:
|
||||
tagArray = append(strings.Split(tags[0], ","), StructTagPriority...)
|
||||
default:
|
||||
tagArray = append(tags, StructTagPriority...)
|
||||
}
|
||||
if len(dataMap) == 0 {
|
||||
return value
|
||||
}
|
||||
return dataMap
|
||||
case reflect.Struct:
|
||||
// Map converting interface check.
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
m := v.MapStrAny()
|
||||
if recursive {
|
||||
for k, v := range m {
|
||||
m[k] = doMapConvertForMapOrStructValue(false, v, recursive, tags...)
|
||||
}
|
||||
}
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
rtField = rt.Field(i)
|
||||
rvField = rv.Field(i)
|
||||
// Only convert the public attributes.
|
||||
fieldName := rtField.Name
|
||||
if !utils.IsLetterUpper(fieldName[0]) {
|
||||
return m
|
||||
}
|
||||
// Using reflect for converting.
|
||||
var (
|
||||
rtField reflect.StructField
|
||||
rvField reflect.Value
|
||||
dataMap = make(map[string]interface{}) // result map.
|
||||
rt = rv.Type() // attribute value type.
|
||||
name = "" // name may be the tag name or the struct attribute name.
|
||||
)
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
rtField = rt.Field(i)
|
||||
rvField = rv.Field(i)
|
||||
// Only convert the public attributes.
|
||||
fieldName := rtField.Name
|
||||
if !utils.IsLetterUpper(fieldName[0]) {
|
||||
continue
|
||||
}
|
||||
name = ""
|
||||
fieldTag := rtField.Tag
|
||||
for _, tag := range tags {
|
||||
if name = fieldTag.Get(tag); name != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = fieldName
|
||||
} else {
|
||||
// Support json tag feature: -, omitempty
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
name = ""
|
||||
fieldTag := rtField.Tag
|
||||
for _, tag := range tagArray {
|
||||
if name = fieldTag.Get(tag); name != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = fieldName
|
||||
} else {
|
||||
// Support json tag feature: -, omitempty
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
array := strings.Split(name, ",")
|
||||
if len(array) > 1 {
|
||||
switch strings.TrimSpace(array[1]) {
|
||||
case "omitempty":
|
||||
if empty.IsEmpty(rvField.Interface()) {
|
||||
continue
|
||||
} else {
|
||||
name = strings.TrimSpace(array[0])
|
||||
}
|
||||
default:
|
||||
array := strings.Split(name, ",")
|
||||
if len(array) > 1 {
|
||||
switch strings.TrimSpace(array[1]) {
|
||||
case "omitempty":
|
||||
if empty.IsEmpty(rvField.Interface()) {
|
||||
continue
|
||||
} else {
|
||||
name = strings.TrimSpace(array[0])
|
||||
}
|
||||
default:
|
||||
name = strings.TrimSpace(array[0])
|
||||
}
|
||||
}
|
||||
if recursive {
|
||||
}
|
||||
if recursive {
|
||||
// Do map converting recursively.
|
||||
var (
|
||||
rvAttrField = rvField
|
||||
rvAttrKind = rvField.Kind()
|
||||
)
|
||||
if rvAttrKind == reflect.Ptr {
|
||||
rvAttrField = rvField.Elem()
|
||||
rvAttrKind = rvAttrField.Kind()
|
||||
}
|
||||
switch rvAttrKind {
|
||||
case reflect.Struct:
|
||||
var (
|
||||
rvAttrField = rvField
|
||||
rvAttrKind = rvField.Kind()
|
||||
hasNoTag = name == fieldName
|
||||
rvAttrInterface = rvAttrField.Interface()
|
||||
)
|
||||
if rvAttrKind == reflect.Ptr {
|
||||
rvAttrField = rvField.Elem()
|
||||
rvAttrKind = rvAttrField.Kind()
|
||||
}
|
||||
if rvAttrKind == reflect.Struct {
|
||||
var (
|
||||
hasNoTag = name == fieldName
|
||||
rvAttrInterface = rvAttrField.Interface()
|
||||
)
|
||||
if hasNoTag && rtField.Anonymous {
|
||||
// It means this attribute field has no tag.
|
||||
// Overwrite the attribute with sub-struct attribute fields.
|
||||
for k, v := range doMapConvert(rvAttrInterface, recursive, tags...) {
|
||||
if hasNoTag && rtField.Anonymous {
|
||||
// It means this attribute field has no tag.
|
||||
// Overwrite the attribute with sub-struct attribute fields.
|
||||
anonymousValue := doMapConvertForMapOrStructValue(false, rvAttrInterface, recursive, tags...)
|
||||
if m, ok := anonymousValue.(map[string]interface{}); ok {
|
||||
for k, v := range m {
|
||||
dataMap[k] = v
|
||||
}
|
||||
} else {
|
||||
// It means this attribute field has desired tag.
|
||||
if m := doMapConvert(rvAttrInterface, recursive, tags...); len(m) > 0 {
|
||||
dataMap[name] = m
|
||||
} else {
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
}
|
||||
dataMap[name] = rvAttrInterface
|
||||
}
|
||||
} else {
|
||||
if rvField.IsValid() {
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
} else {
|
||||
dataMap[name] = nil
|
||||
}
|
||||
// It means this attribute field has desired tag.
|
||||
dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, recursive, tags...)
|
||||
}
|
||||
} else {
|
||||
|
||||
// The struct attribute is type of slice.
|
||||
case reflect.Array, reflect.Slice:
|
||||
length := rvField.Len()
|
||||
if length == 0 {
|
||||
dataMap[name] = rvField.Interface()
|
||||
break
|
||||
}
|
||||
array := make([]interface{}, length)
|
||||
for i := 0; i < length; i++ {
|
||||
array[i] = doMapConvertForMapOrStructValue(false, rvField.Index(i), recursive, tags...)
|
||||
}
|
||||
dataMap[name] = array
|
||||
|
||||
default:
|
||||
if rvField.IsValid() {
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
} else {
|
||||
dataMap[name] = nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No recursive map value converting
|
||||
if rvField.IsValid() {
|
||||
dataMap[name] = rv.Field(i).Interface()
|
||||
} else {
|
||||
dataMap[name] = nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if len(dataMap) == 0 {
|
||||
return value
|
||||
}
|
||||
return dataMap
|
||||
|
||||
// The given value is type of slice.
|
||||
case reflect.Array, reflect.Slice:
|
||||
length := rv.Len()
|
||||
if length == 0 {
|
||||
break
|
||||
}
|
||||
array := make([]interface{}, rv.Len())
|
||||
for i := 0; i < length; i++ {
|
||||
array[i] = doMapConvertForMapOrStructValue(false, rv.Index(i), recursive, tags...)
|
||||
}
|
||||
return array
|
||||
}
|
||||
return dataMap
|
||||
return value
|
||||
}
|
||||
|
||||
// MapStrStr converts <value> to map[string]string.
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -63,6 +64,18 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
}
|
||||
}()
|
||||
|
||||
// If given <params> is JSON, it then uses json.Unmarshal doing the converting.
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
return json.Unmarshal(r, pointer)
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
return json.Unmarshal(paramsBytes, pointer)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalValue.
|
||||
// Assign value with interface UnmarshalValue.
|
||||
// Note that only pointer can implement interface UnmarshalValue.
|
||||
@ -259,8 +272,11 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, rec
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
if e := recover(); e != nil {
|
||||
err = bindVarToReflectValue(structFieldValue, value, recursive, mapping...)
|
||||
if err != nil {
|
||||
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if empty.IsNil(value) {
|
||||
@ -283,8 +299,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, re
|
||||
v.Set(value)
|
||||
return nil
|
||||
} else if v, ok := structFieldValue.Interface().(apiUnmarshalValue); ok {
|
||||
err = v.UnmarshalValue(value)
|
||||
if err == nil {
|
||||
if err = v.UnmarshalValue(value); err == nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package gconv
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -42,6 +43,18 @@ func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...ma
|
||||
err = gerror.NewfSkip(1, "%v", e)
|
||||
}
|
||||
}()
|
||||
// If given <params> is JSON, it then uses json.Unmarshal doing the converting.
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
return json.Unmarshal(r, pointer)
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
return json.Unmarshal(paramsBytes, pointer)
|
||||
}
|
||||
}
|
||||
// Pointer type check.
|
||||
pointerRv, ok := pointer.(reflect.Value)
|
||||
if !ok {
|
||||
pointerRv = reflect.ValueOf(pointer)
|
||||
@ -49,6 +62,7 @@ func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...ma
|
||||
return gerror.Newf("pointer should be type of pointer, but got: %v", kind)
|
||||
}
|
||||
}
|
||||
// Converting <params> to map slice.
|
||||
paramsMaps := Maps(params)
|
||||
// If <params> is an empty slice, no conversion.
|
||||
if len(paramsMaps) == 0 {
|
||||
|
||||
@ -981,3 +981,23 @@ func Test_Struct_To_Struct(t *testing.T) {
|
||||
t.Assert(TestA.Date, TestB.Date)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_WithJson(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
type B struct {
|
||||
A
|
||||
Score int
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
b1 := &B{}
|
||||
b1.Name = "john"
|
||||
b1.Score = 100
|
||||
b, _ := json.Marshal(b1)
|
||||
b2 := &B{}
|
||||
err := gconv.Struct(b, b2)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(b2, b1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
)
|
||||
|
||||
@ -16,11 +17,24 @@ func Throw(exception interface{}) {
|
||||
panic(exception)
|
||||
}
|
||||
|
||||
// Try implements try... logistics using internal panic...recover.
|
||||
// It returns error if any exception occurs, or else it returns nil.
|
||||
func Try(try func()) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf(`%v`, e)
|
||||
}
|
||||
}()
|
||||
try()
|
||||
return
|
||||
}
|
||||
|
||||
// TryCatch implements try...catch... logistics using internal panic...recover.
|
||||
func TryCatch(try func(), catch ...func(exception interface{})) {
|
||||
// It automatically calls function <catch> if any exception occurs ans passes the exception as an error.
|
||||
func TryCatch(try func(), catch ...func(exception error)) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil && len(catch) > 0 {
|
||||
catch[0](e)
|
||||
catch[0](fmt.Errorf(`%v`, e))
|
||||
}
|
||||
}()
|
||||
try()
|
||||
|
||||
@ -15,6 +15,16 @@ import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// apiString is used for type assert api for String().
|
||||
type apiString interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// apiMapStrAny is the interface support for converting struct parameter to map.
|
||||
type apiMapStrAny interface {
|
||||
MapStrAny() map[string]interface{}
|
||||
}
|
||||
|
||||
// Dump prints variables <i...> to stdout with more manually readable.
|
||||
func Dump(i ...interface{}) {
|
||||
s := Export(i...)
|
||||
@ -27,11 +37,16 @@ func Dump(i ...interface{}) {
|
||||
func Export(i ...interface{}) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for _, v := range i {
|
||||
if b, ok := v.([]byte); ok {
|
||||
buffer.Write(b)
|
||||
} else {
|
||||
rv := reflect.ValueOf(v)
|
||||
kind := rv.Kind()
|
||||
switch r := v.(type) {
|
||||
case []byte:
|
||||
buffer.Write(r)
|
||||
case string:
|
||||
buffer.WriteString(r)
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(v)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -39,8 +54,14 @@ func Export(i ...interface{}) string {
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
v = gconv.Interfaces(v)
|
||||
case reflect.Map, reflect.Struct:
|
||||
case reflect.Map:
|
||||
v = gconv.Map(v)
|
||||
case reflect.Struct:
|
||||
if r, ok := v.(apiMapStrAny); ok {
|
||||
v = r.MapStrAny()
|
||||
} else if r, ok := v.(apiString); ok {
|
||||
v = r.String()
|
||||
}
|
||||
}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
|
||||
@ -25,7 +25,7 @@ func Benchmark_TryCatch(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
TryCatch(func() {
|
||||
|
||||
}, func(err interface{}) {
|
||||
}, func(err error) {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@ -21,6 +21,15 @@ func Test_Dump(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Try(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := `gutil Try test`
|
||||
t.Assert(gutil.Try(func() {
|
||||
panic(s)
|
||||
}), s)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_TryCatch(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.TryCatch(func() {
|
||||
@ -32,21 +41,19 @@ func Test_TryCatch(t *testing.T) {
|
||||
gutil.TryCatch(func() {
|
||||
panic("gutil TryCatch test")
|
||||
|
||||
}, func(err interface{}) {
|
||||
}, func(err error) {
|
||||
t.Assert(err, "gutil TryCatch test")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_IsEmpty(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gutil.IsEmpty(1), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Throw(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
defer func() {
|
||||
t.Assert(recover(), "gutil Throw test")
|
||||
|
||||
@ -8,7 +8,7 @@ package gvalid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/net/gipv4"
|
||||
"github.com/gogf/gf/net/gipv6"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
@ -452,7 +452,7 @@ func doCheckBuildInRules(
|
||||
|
||||
// Json.
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(valueStr)); err == nil {
|
||||
if json.Valid([]byte(valueStr)) {
|
||||
match = true
|
||||
}
|
||||
|
||||
|
||||
@ -87,20 +87,27 @@ func CheckMap(params interface{}, rules interface{}, messages ...CustomMsg) *Err
|
||||
if v, ok := data[key]; ok {
|
||||
value = v
|
||||
}
|
||||
// It checks each rule and its value in loop.
|
||||
if e := doCheck(key, value, rule, customMsgs[key], data); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// ===========================================================
|
||||
// If value is nil or empty string and has no required* rules,
|
||||
// clear the error message.
|
||||
// Only in map and struct validations, if value is nil or empty
|
||||
// string and has no required* rules, it clears the error message.
|
||||
// ===========================================================
|
||||
if gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
for k := range item {
|
||||
// Default required rules.
|
||||
if _, ok := mustCheckRulesEvenValueEmpty[k]; ok {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
// Custom rules are also required in default.
|
||||
if _, ok := customRuleFuncMap[k]; ok {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !required {
|
||||
continue
|
||||
|
||||
@ -149,20 +149,27 @@ func CheckStruct(object interface{}, rules interface{}, messages ...CustomMsg) *
|
||||
if v, ok := params[key]; ok {
|
||||
value = v
|
||||
}
|
||||
// It checks each rule and its value in loop.
|
||||
if e := doCheck(key, value, rule, customMessage[key], params); e != nil {
|
||||
_, item := e.FirstItem()
|
||||
// ===========================================================
|
||||
// If value is nil or empty string and has no required* rules,
|
||||
// clear the error message.
|
||||
// Only in map and struct validations, if value is nil or empty
|
||||
// string and has no required* rules, it clears the error message.
|
||||
// ===========================================================
|
||||
if value == nil || gconv.String(value) == "" {
|
||||
required := false
|
||||
// rule => error
|
||||
for k := range item {
|
||||
// Default required rules.
|
||||
if _, ok := mustCheckRulesEvenValueEmpty[k]; ok {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
// Custom rules are also required in default.
|
||||
if _, ok := customRuleFuncMap[k]; ok {
|
||||
required = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !required {
|
||||
continue
|
||||
|
||||
@ -16,6 +16,7 @@ type RuleFunc func(rule string, value interface{}, message string, params map[st
|
||||
|
||||
var (
|
||||
// customRuleFuncMap stores the custom rule functions.
|
||||
// map[Rule]RuleFunc
|
||||
customRuleFuncMap = make(map[string]RuleFunc)
|
||||
)
|
||||
|
||||
|
||||
@ -106,3 +106,48 @@ func Test_CustomRule2(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CustomRule_AllowEmpty(t *testing.T) {
|
||||
rule := "allow-empty-str"
|
||||
err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
s := gconv.String(value)
|
||||
if len(s) == 0 || s == "gf" {
|
||||
return nil
|
||||
}
|
||||
return errors.New(message)
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
errStr := "error"
|
||||
t.Assert(gvalid.Check("", rule, errStr).String(), "")
|
||||
t.Assert(gvalid.Check("gf", rule, errStr).String(), "")
|
||||
t.Assert(gvalid.Check("gf2", rule, errStr).String(), errStr)
|
||||
})
|
||||
// Error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value string `v:"uid@allow-empty-str#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: "",
|
||||
Data: "123456",
|
||||
}
|
||||
err := gvalid.CheckStruct(st, nil)
|
||||
t.Assert(err.String(), "")
|
||||
})
|
||||
// No error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value string `v:"uid@allow-empty-str#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: "john",
|
||||
Data: "123456",
|
||||
}
|
||||
err := gvalid.CheckStruct(st, nil)
|
||||
t.Assert(err.String(), "自定义错误")
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.13.7"
|
||||
const VERSION = "v1.14.2"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
Reference in New Issue
Block a user