mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 11:18:02 +08:00
Merge branch 'gogf:master' into master
This commit is contained in:
commit
6f64c26349
@ -1776,7 +1776,7 @@ CREATE TABLE %s (
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3)
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5)
|
||||
})
|
||||
db.SetDebug(true)
|
||||
|
||||
// Result ScanList with struct elements and pointer attributes.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []Entity
|
||||
|
37
internal/utils/utils_list.go
Normal file
37
internal/utils/utils_list.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package utils
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ListToMapByKey converts `list` to a map[string]interface{} of which key is specified by `key`.
|
||||
// Note that the item value may be type of slice.
|
||||
func ListToMapByKey(list []map[string]interface{}, key string) map[string]interface{} {
|
||||
var (
|
||||
s = ""
|
||||
m = make(map[string]interface{})
|
||||
tempMap = make(map[string][]interface{})
|
||||
hasMultiValues bool
|
||||
)
|
||||
for _, item := range list {
|
||||
if k, ok := item[key]; ok {
|
||||
s = fmt.Sprintf(`%v`, k)
|
||||
tempMap[s] = append(tempMap[s], item)
|
||||
if len(tempMap[s]) > 1 {
|
||||
hasMultiValues = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range tempMap {
|
||||
if hasMultiValues {
|
||||
m[k] = v
|
||||
} else {
|
||||
m[k] = v[0]
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
26
internal/utils/utils_map.go
Normal file
26
internal/utils/utils_map.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package utils
|
||||
|
||||
// MapPossibleItemByKey tries to find the possible key-value pair for given key ignoring cases and symbols.
|
||||
//
|
||||
// Note that this function might be of low performance.
|
||||
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
if v, ok := data[key]; ok {
|
||||
return key, v
|
||||
}
|
||||
// Loop checking.
|
||||
for k, v := range data {
|
||||
if EqualFoldWithoutChars(k, key) {
|
||||
return k, v
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
@ -327,14 +327,7 @@ func Split(str, delimiter string) []string {
|
||||
// and calls Trim to every element of this array. It ignores the elements
|
||||
// which are empty after Trim.
|
||||
func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
|
||||
array := make([]string, 0)
|
||||
for _, v := range strings.Split(str, delimiter) {
|
||||
v = Trim(v, characterMask...)
|
||||
if v != "" {
|
||||
array = append(array, v)
|
||||
}
|
||||
}
|
||||
return array
|
||||
return utils.SplitAndTrim(str, delimiter, characterMask...)
|
||||
}
|
||||
|
||||
// Join concatenates the elements of `array` to create a single string. The separator string
|
||||
|
@ -7,8 +7,11 @@
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/structs"
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -38,7 +41,18 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
|
||||
}
|
||||
pointerKind = pointerType.Kind()
|
||||
if pointerKind != reflect.Ptr {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "params should be type of pointer, but got type: %v", pointerKind)
|
||||
if pointerValue.CanAddr() {
|
||||
pointerValue = pointerValue.Addr()
|
||||
pointerType = pointerValue.Type()
|
||||
pointerKind = pointerType.Kind()
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"params should be type of pointer, but got type: %v",
|
||||
pointerType,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
// Direct assignment checks!
|
||||
var (
|
||||
@ -94,3 +108,410 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
|
||||
return doStruct(params, pointer, keyToAttributeNameMapping, "")
|
||||
}
|
||||
}
|
||||
|
||||
// ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
//
|
||||
// Usage example 1: Normal attribute struct relation:
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// Course string
|
||||
// }
|
||||
// type Entity struct {
|
||||
// User *EntityUser
|
||||
// UserDetail *EntityUserDetail
|
||||
// UserScores []*EntityUserScores
|
||||
// }
|
||||
// var users []*Entity
|
||||
// ScanList(records, &users, "User")
|
||||
// ScanList(records, &users, "User", "uid")
|
||||
// ScanList(records, &users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(records, &users, "UserScores", "User", "uid:Uid")
|
||||
// ScanList(records, &users, "UserScores", "User", "uid")
|
||||
//
|
||||
//
|
||||
// Usage example 2: Embedded attribute struct relation:
|
||||
// type EntityUser struct {
|
||||
// Uid int
|
||||
// Name string
|
||||
// }
|
||||
// type EntityUserDetail struct {
|
||||
// Uid int
|
||||
// Address string
|
||||
// }
|
||||
// type EntityUserScores struct {
|
||||
// Id int
|
||||
// Uid int
|
||||
// Score int
|
||||
// }
|
||||
// type Entity struct {
|
||||
// EntityUser
|
||||
// UserDetail EntityUserDetail
|
||||
// UserScores []EntityUserScores
|
||||
// }
|
||||
//
|
||||
// var users []*Entity
|
||||
// ScanList(records, &users)
|
||||
// ScanList(records, &users, "UserDetail", "uid")
|
||||
// ScanList(records, &users, "UserScores", "uid")
|
||||
//
|
||||
//
|
||||
// The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
//
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
|
||||
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
|
||||
// given `relation` parameter.
|
||||
//
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
|
||||
var (
|
||||
relationAttrName string
|
||||
relationFields string
|
||||
)
|
||||
switch len(relationAttrNameAndFields) {
|
||||
case 2:
|
||||
relationAttrName = relationAttrNameAndFields[0]
|
||||
relationFields = relationAttrNameAndFields[1]
|
||||
case 1:
|
||||
relationFields = relationAttrNameAndFields[0]
|
||||
}
|
||||
return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
|
||||
}
|
||||
|
||||
// doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
|
||||
// Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
|
||||
func doScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string) (err error) {
|
||||
var (
|
||||
maps = Maps(structSlice)
|
||||
)
|
||||
if len(maps) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Necessary checks for parameters.
|
||||
if bindToAttrName == "" {
|
||||
return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
|
||||
}
|
||||
|
||||
if relationAttrName == "." {
|
||||
relationAttrName = ""
|
||||
}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(structSlicePointer)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
if reflectKind == reflect.Interface {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Ptr {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
|
||||
reflectKind,
|
||||
)
|
||||
}
|
||||
length := len(maps)
|
||||
if length == 0 {
|
||||
// The pointed slice is not empty.
|
||||
if reflectValue.Len() > 0 {
|
||||
// It here checks if it has struct item, which is already initialized.
|
||||
// It then returns error to warn the developer its empty and no conversion.
|
||||
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
}
|
||||
// Do nothing for empty struct slice.
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
arrayValue reflect.Value // Like: []*Entity
|
||||
arrayItemType reflect.Type // Like: *Entity
|
||||
reflectType = reflect.TypeOf(structSlicePointer)
|
||||
)
|
||||
if reflectValue.Len() > 0 {
|
||||
arrayValue = reflectValue
|
||||
} else {
|
||||
arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
|
||||
}
|
||||
|
||||
// Slice element item.
|
||||
arrayItemType = arrayValue.Index(0).Type()
|
||||
|
||||
// Relation variables.
|
||||
var (
|
||||
relationDataMap map[string]interface{}
|
||||
relationFromFieldName string // Eg: relationKV: id:uid -> id
|
||||
relationBindToFieldName string // Eg: relationKV: id:uid -> uid
|
||||
)
|
||||
if len(relationFields) > 0 {
|
||||
// The relation key string of table filed name and attribute name
|
||||
// can be joined with char '=' or ':'.
|
||||
array := utils.SplitAndTrim(relationFields, "=")
|
||||
if len(array) == 1 {
|
||||
// Compatible with old splitting char ':'.
|
||||
array = utils.SplitAndTrim(relationFields, ":")
|
||||
}
|
||||
if len(array) == 1 {
|
||||
// The relation names are the same.
|
||||
array = []string{relationFields, relationFields}
|
||||
}
|
||||
if len(array) == 2 {
|
||||
// Defined table field to relation attribute name.
|
||||
// Like:
|
||||
// uid:Uid
|
||||
// uid:UserId
|
||||
relationFromFieldName = array[0]
|
||||
relationBindToFieldName = array[1]
|
||||
if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related table field name "%s" from given relation fields "%s"`,
|
||||
relationFromFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationFromFieldName = key
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCode(
|
||||
gcode.CodeInvalidParameter,
|
||||
`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
|
||||
)
|
||||
}
|
||||
if relationFromFieldName != "" {
|
||||
// Note that the value might be type of slice.
|
||||
relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
|
||||
}
|
||||
if len(relationDataMap) == 0 {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find the relation data map, maybe invalid relation fields given "%v"`,
|
||||
relationFields,
|
||||
)
|
||||
}
|
||||
}
|
||||
// Bind to target attribute.
|
||||
var (
|
||||
ok bool
|
||||
bindToAttrValue reflect.Value
|
||||
bindToAttrKind reflect.Kind
|
||||
bindToAttrType reflect.Type
|
||||
bindToAttrField reflect.StructField
|
||||
)
|
||||
if arrayItemType.Kind() == reflect.Ptr {
|
||||
if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
}
|
||||
bindToAttrType = bindToAttrField.Type
|
||||
bindToAttrKind = bindToAttrType.Kind()
|
||||
|
||||
// Bind to relation conditions.
|
||||
var (
|
||||
relationFromAttrValue reflect.Value
|
||||
relationFromAttrField reflect.Value
|
||||
relationBindToFieldNameChecked bool
|
||||
)
|
||||
for i := 0; i < arrayValue.Len(); i++ {
|
||||
arrayElemValue := arrayValue.Index(i)
|
||||
// The FieldByName should be called on non-pointer reflect.Value.
|
||||
if arrayElemValue.Kind() == reflect.Ptr {
|
||||
// Like: []*Entity
|
||||
arrayElemValue = arrayElemValue.Elem()
|
||||
if !arrayElemValue.IsValid() {
|
||||
// The element is nil, then create one and set it to the slice.
|
||||
// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
|
||||
// For example:
|
||||
// reflect.New(itemType.Elem()) => *Entity
|
||||
// reflect.New(itemType.Elem()).Elem() => Entity
|
||||
arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
|
||||
arrayValue.Index(i).Set(arrayElemValue.Addr())
|
||||
}
|
||||
} else {
|
||||
// Like: []Entity
|
||||
}
|
||||
bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
|
||||
if relationAttrName != "" {
|
||||
// Attribute value of current slice element.
|
||||
relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
|
||||
if relationFromAttrValue.Kind() == reflect.Ptr {
|
||||
relationFromAttrValue = relationFromAttrValue.Elem()
|
||||
}
|
||||
} else {
|
||||
// Current slice element.
|
||||
relationFromAttrValue = arrayElemValue
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
// Check and find possible bind to attribute name.
|
||||
if relationFields != "" && !relationBindToFieldNameChecked {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if !relationFromAttrField.IsValid() {
|
||||
var (
|
||||
filedMap, _ = structs.FieldMap(structs.FieldMapInput{
|
||||
Pointer: relationFromAttrValue,
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
)
|
||||
if key, _ := utils.MapPossibleItemByKey(Map(filedMap), relationBindToFieldName); key == "" {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`cannot find possible related attribute name "%s" from given relation fields "%s"`,
|
||||
relationBindToFieldName,
|
||||
relationFields,
|
||||
)
|
||||
} else {
|
||||
relationBindToFieldName = key
|
||||
}
|
||||
}
|
||||
relationBindToFieldNameChecked = true
|
||||
}
|
||||
switch bindToAttrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
//results := make(Result, 0)
|
||||
results := make([]interface{}, 0)
|
||||
for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
|
||||
item := v
|
||||
results = append(results, item)
|
||||
}
|
||||
if err = Structs(results, bindToAttrValue.Addr()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
return gerror.NewCodef(
|
||||
gcode.CodeInvalidParameter,
|
||||
`relationKey should not be empty as field "%s" is slice`,
|
||||
bindToAttrName,
|
||||
)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
var element reflect.Value
|
||||
if bindToAttrValue.IsNil() {
|
||||
element = reflect.New(bindToAttrType.Elem()).Elem()
|
||||
} else {
|
||||
element = bindToAttrValue.Elem()
|
||||
}
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
v := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if utils.IsSlice(v) {
|
||||
if err = Struct(SliceAny(v)[0], element); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
v := maps[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(v, element); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bindToAttrValue.Set(element.Addr())
|
||||
|
||||
case reflect.Struct:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
|
||||
if relationFromAttrField.IsValid() {
|
||||
relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if utils.IsSlice(relationDataItem) {
|
||||
if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the attribute does not exist yet.
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
|
||||
}
|
||||
} else {
|
||||
if i >= len(maps) {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
relationDataItem := maps[i]
|
||||
if relationDataItem == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = Struct(relationDataItem, bindToAttrValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
|
||||
}
|
||||
}
|
||||
reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
|
||||
return nil
|
||||
}
|
||||
|
@ -319,3 +319,285 @@ func Test_Scan_SameType_Just_Assign(t *testing.T) {
|
||||
t.Assert(*m1["int"], *m2["int"])
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanList_Basic(t *testing.T) {
|
||||
// Struct attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
User EntityUser
|
||||
UserDetail EntityUserDetail
|
||||
UserScores []EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []Entity
|
||||
entityUsers = []EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.ScanList(entityUsers, &entities, "User")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].User, entityUsers[0])
|
||||
t.Assert(entities[1].User, entityUsers[1])
|
||||
t.Assert(entities[2].User, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, EntityUserDetail{})
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
// Pointer attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
User *EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []*Entity
|
||||
entityUsers = []*EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []*EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []*EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.ScanList(entityUsers, &entities, "User")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "User", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].User, entityUsers[0])
|
||||
t.Assert(entities[1].User, entityUsers[1])
|
||||
t.Assert(entities[2].User, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, nil)
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ScanList_Embedded(t *testing.T) {
|
||||
// Struct attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
EntityUser
|
||||
UserDetail EntityUserDetail
|
||||
UserScores []EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []Entity
|
||||
entityUsers = []EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.Scan(entityUsers, &entities)
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].EntityUser, entityUsers[0])
|
||||
t.Assert(entities[1].EntityUser, entityUsers[1])
|
||||
t.Assert(entities[2].EntityUser, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, EntityUserDetail{})
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
// Pointer attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int
|
||||
Address string
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int
|
||||
Uid int
|
||||
Score int
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
*EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
entities []Entity
|
||||
entityUsers = []EntityUser{
|
||||
{Uid: 1, Name: "name1"},
|
||||
{Uid: 2, Name: "name2"},
|
||||
{Uid: 3, Name: "name3"},
|
||||
}
|
||||
userDetails = []EntityUserDetail{
|
||||
{Uid: 1, Address: "address1"},
|
||||
{Uid: 2, Address: "address2"},
|
||||
}
|
||||
userScores = []EntityUserScores{
|
||||
{Id: 10, Uid: 1, Score: 100},
|
||||
{Id: 11, Uid: 1, Score: 60},
|
||||
{Id: 20, Uid: 2, Score: 99},
|
||||
}
|
||||
)
|
||||
err = gconv.Scan(entityUsers, &entities)
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userDetails, &entities, "UserDetail", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gconv.ScanList(userScores, &entities, "UserScores", "uid")
|
||||
t.AssertNil(err)
|
||||
|
||||
t.Assert(len(entities), 3)
|
||||
t.Assert(entities[0].EntityUser, entityUsers[0])
|
||||
t.Assert(entities[1].EntityUser, entityUsers[1])
|
||||
t.Assert(entities[2].EntityUser, entityUsers[2])
|
||||
|
||||
t.Assert(entities[0].UserDetail, userDetails[0])
|
||||
t.Assert(entities[1].UserDetail, userDetails[1])
|
||||
t.Assert(entities[2].UserDetail, nil)
|
||||
|
||||
t.Assert(len(entities[0].UserScores), 2)
|
||||
t.Assert(entities[0].UserScores[0], userScores[0])
|
||||
t.Assert(entities[0].UserScores[1], userScores[1])
|
||||
|
||||
t.Assert(len(entities[1].UserScores), 1)
|
||||
t.Assert(entities[1].UserScores[0], userScores[2])
|
||||
|
||||
t.Assert(len(entities[2].UserScores), 0)
|
||||
})
|
||||
}
|
||||
|
@ -15,6 +15,16 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// iString is used for type assert api for String().
|
||||
type iString interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// iMarshalJSON is the interface for custom Json marshaling.
|
||||
type iMarshalJSON interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
// ExportOption specifies the behavior of function Export.
|
||||
type ExportOption struct {
|
||||
WithoutType bool // WithoutType specifies exported content has no type information.
|
||||
@ -151,7 +161,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
"%s%v:%s",
|
||||
newIndent,
|
||||
mapKeyStr,
|
||||
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
))
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
@ -159,7 +169,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
newIndent,
|
||||
mapKey.Type().String(),
|
||||
mapKeyStr,
|
||||
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
))
|
||||
}
|
||||
doExport(reflectValue.MapIndex(mapKey).Interface(), newIndent, buffer, option)
|
||||
@ -173,10 +183,31 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
||||
})
|
||||
if len(structFields) == 0 {
|
||||
if option.WithoutType {
|
||||
buffer.WriteString("{}")
|
||||
var (
|
||||
structContentStr = ""
|
||||
attributeCountStr = "0"
|
||||
)
|
||||
if v, ok := value.(iString); ok {
|
||||
structContentStr = v.String()
|
||||
} else if v, ok := value.(iMarshalJSON); ok {
|
||||
b, _ := v.MarshalJSON()
|
||||
structContentStr = string(b)
|
||||
}
|
||||
if structContentStr == "" {
|
||||
structContentStr = "{}"
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("%s(0) {}", reflectTypeName))
|
||||
structContentStr = fmt.Sprintf(`"%s"`, gstr.AddSlashes(structContentStr))
|
||||
attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2)
|
||||
}
|
||||
if option.WithoutType {
|
||||
buffer.WriteString(structContentStr)
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
"%s(%s) %s",
|
||||
reflectTypeName,
|
||||
attributeCountStr,
|
||||
structContentStr,
|
||||
))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -202,7 +233,7 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
||||
"%s%s:%s",
|
||||
newIndent,
|
||||
field.Name(),
|
||||
gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||
))
|
||||
doExport(field.Value.Interface(), newIndent, buffer, option)
|
||||
buffer.WriteString(",\n")
|
||||
|
@ -7,6 +7,7 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/utils"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -130,3 +131,9 @@ func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) [
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// ListToMapByKey converts `list` to a map[string]interface{} of which key is specified by `key`.
|
||||
// Note that the item value may be type of slice.
|
||||
func ListToMapByKey(list []map[string]interface{}, key string) map[string]interface{} {
|
||||
return utils.ListToMapByKey(list, key)
|
||||
}
|
||||
|
@ -67,19 +67,7 @@ func MapMergeCopy(src ...map[string]interface{}) (copy map[string]interface{}) {
|
||||
//
|
||||
// Note that this function might be of low performance.
|
||||
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
if v, ok := data[key]; ok {
|
||||
return key, v
|
||||
}
|
||||
// Loop checking.
|
||||
for k, v := range data {
|
||||
if utils.EqualFoldWithoutChars(k, key) {
|
||||
return k, v
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
return utils.MapPossibleItemByKey(data, key)
|
||||
}
|
||||
|
||||
// MapContainsPossibleKey checks if the given `key` is contained in given map `data`.
|
||||
|
128
util/gutil/gutil_z_unit_dump_test.go
Executable file
128
util/gutil/gutil_z_unit_dump_test.go
Executable file
@ -0,0 +1,128 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gutil_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
func Test_Dump(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string
|
||||
CreatedAt *gtime.Time
|
||||
SetMap map[string]*SetSpecInfo
|
||||
SetSlice []SetSpecInfo
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
CreatedAt: gtime.Now(),
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.Dump(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.Dump(req)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDumpWithType(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string
|
||||
CreatedAt *gtime.Time
|
||||
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
CreatedAt: gtime.Now(),
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.DumpWithType(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.DumpWithType(req)
|
||||
gutil.DumpWithType([][]byte{[]byte("hello")})
|
||||
})
|
||||
}
|
@ -8,129 +8,12 @@ package gutil_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
|
||||
func Test_Dump(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string `des:"实例名称"`
|
||||
Product string `des:"业务类型"`
|
||||
Region string `v:"required" des:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
Product: "goframe",
|
||||
Region: "cd",
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.Dump(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.Dump(req)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDumpWithType(t *testing.T) {
|
||||
type CommonReq struct {
|
||||
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
|
||||
ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"`
|
||||
}
|
||||
type SetSpecInfo struct {
|
||||
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"`
|
||||
Shards int32 `des:"shards 分片数" sum:"Shards Summary"`
|
||||
Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"`
|
||||
}
|
||||
type CreateResourceReq struct {
|
||||
CommonReq
|
||||
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"`
|
||||
Name string `des:"实例名称"`
|
||||
Product string `des:"业务类型"`
|
||||
Region string `v:"required" des:"区域"`
|
||||
SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"`
|
||||
SetSlice []SetSpecInfo `v:"required" des:"配置Slice"`
|
||||
Handler ghttp.HandlerFunc
|
||||
internal string
|
||||
}
|
||||
req := &CreateResourceReq{
|
||||
CommonReq: CommonReq{
|
||||
AppId: 12345678,
|
||||
ResourceId: "tdchqy-xxx",
|
||||
},
|
||||
Name: "john",
|
||||
Product: "goframe",
|
||||
Region: "cd",
|
||||
SetMap: map[string]*SetSpecInfo{
|
||||
"test1": {
|
||||
StorageType: "ssd",
|
||||
Shards: 2,
|
||||
Params: []string{"a", "b", "c"},
|
||||
},
|
||||
"test2": {
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{},
|
||||
},
|
||||
},
|
||||
SetSlice: []SetSpecInfo{
|
||||
{
|
||||
StorageType: "hssd",
|
||||
Shards: 10,
|
||||
Params: []string{"h"},
|
||||
},
|
||||
},
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gutil.DumpWithType(map[int]int{
|
||||
100: 100,
|
||||
})
|
||||
gutil.DumpWithType(req)
|
||||
gutil.DumpWithType([][]byte{[]byte("hello")})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Try(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s := `gutil Try test`
|
||||
|
Loading…
Reference in New Issue
Block a user