1
0
mirror of https://github.com/gogf/gf.git synced 2025-04-05 11:18:50 +08:00

add embedded struct fields overwrite feature for package internal/structs

This commit is contained in:
John Guo 2021-10-26 21:57:56 +08:00
parent e0674ee7fe
commit a19ba3d530
3 changed files with 71 additions and 13 deletions

View File

@ -116,9 +116,6 @@ type FieldsInput struct {
// RecursiveOption specifies the way retrieving the fields recursively if the attribute
// is an embedded struct. It is RecursiveOptionNone in default.
RecursiveOption int
// fieldFilterMap is used internally for repeated fields filtering.
fieldFilterMap map[string]struct{}
}
type FieldMapInput struct {
@ -136,19 +133,28 @@ type FieldMapInput struct {
// Fields retrieves and returns the fields of `pointer` as slice.
func Fields(in FieldsInput) ([]*Field, error) {
if in.fieldFilterMap == nil {
in.fieldFilterMap = make(map[string]struct{})
}
var (
retrievedFields = make([]*Field, 0)
ok bool
fieldFilterMap = make(map[string]struct{})
retrievedFields = make([]*Field, 0)
currentLevelFieldMap = make(map[string]*Field)
)
rangeFields, err := getFieldValues(in.Pointer)
if err != nil {
return nil, err
}
for index := 0; index < len(rangeFields); index++ {
field := rangeFields[index]
if _, ok := in.fieldFilterMap[field.Name()]; ok {
if !field.IsExported() {
continue
}
currentLevelFieldMap[field.Name()] = field
}
for index := 0; index < len(rangeFields); index++ {
field := rangeFields[index]
if _, ok = fieldFilterMap[field.Name()]; ok {
continue
}
// It only retrieves exported attributes.
@ -167,18 +173,32 @@ func Fields(in FieldsInput) ([]*Field, error) {
structFields, err := Fields(FieldsInput{
Pointer: field.Value,
RecursiveOption: in.RecursiveOption,
fieldFilterMap: in.fieldFilterMap,
})
if err != nil {
return nil, err
}
retrievedFields = append(retrievedFields, structFields...)
// The current level fields can overwrite the sub-struct fields with the same name.
for i := 0; i < len(structFields); i++ {
var (
structField = structFields[i]
fieldName = structField.Name()
)
if _, ok = fieldFilterMap[fieldName]; ok {
continue
}
fieldFilterMap[fieldName] = struct{}{}
if v := currentLevelFieldMap[fieldName]; v == nil {
retrievedFields = append(retrievedFields, structField)
} else {
retrievedFields = append(retrievedFields, v)
}
}
continue
}
}
continue
}
in.fieldFilterMap[field.Name()] = struct{}{}
fieldFilterMap[field.Name()] = struct{}{}
retrievedFields = append(retrievedFields, field)
}
return retrievedFields, nil

View File

@ -125,7 +125,7 @@ func Test_Fields(t *testing.T) {
})
}
func Test_Fields_WithEmbedded(t *testing.T) {
func Test_Fields_WithEmbedded1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type B struct {
Name string
@ -149,6 +149,44 @@ func Test_Fields_WithEmbedded(t *testing.T) {
})
}
func Test_Fields_WithEmbedded2(t *testing.T) {
type MetaNode struct {
Id uint `orm:"id,primary" description:""`
Capacity string `orm:"capacity" description:"Capacity string"`
Allocatable string `orm:"allocatable" description:"Allocatable string"`
Status string `orm:"status" description:"Status string"`
}
type MetaNodeZone struct {
Nodes uint
Clusters uint
Disk uint
Cpu uint
Memory uint
Zone string
}
type MetaNodeItem struct {
MetaNode
Capacity []MetaNodeZone `dc:"Capacity []MetaNodeZone"`
Allocatable []MetaNodeZone `dc:"Allocatable []MetaNodeZone"`
}
gtest.C(t, func(t *gtest.T) {
r, err := structs.Fields(structs.FieldsInput{
Pointer: new(MetaNodeItem),
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
})
t.AssertNil(err)
t.Assert(len(r), 4)
t.Assert(r[0].Name(), `Id`)
t.Assert(r[1].Name(), `Capacity`)
t.Assert(r[1].TagStr(), `dc:"Capacity []MetaNodeZone"`)
t.Assert(r[2].Name(), `Allocatable`)
t.Assert(r[2].TagStr(), `dc:"Allocatable []MetaNodeZone"`)
t.Assert(r[3].Name(), `Status`)
})
}
// Filter repeated fields when there is embedded struct.
func Test_Fields_WithEmbedded_Filter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {

View File

@ -116,7 +116,7 @@ func (v *Validator) checkRequired(value interface{}, ruleKey, rulePattern string
}
}
// Required if all of given fields are empty.
// Required if all given fields are empty.
// Example: required-with:id,name
case "required-without-all":
required = true