From a19ba3d5307ef7c20bc7f1bb6ee1e554ac346e76 Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 26 Oct 2021 21:57:56 +0800 Subject: [PATCH] add embedded struct fields overwrite feature for package internal/structs --- internal/structs/structs_field.go | 42 ++++++++++++++----- internal/structs/structs_z_unit_test.go | 40 +++++++++++++++++- util/gvalid/gvalid_validator_rule_required.go | 2 +- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/internal/structs/structs_field.go b/internal/structs/structs_field.go index 4b0955b68..518258b00 100644 --- a/internal/structs/structs_field.go +++ b/internal/structs/structs_field.go @@ -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 diff --git a/internal/structs/structs_z_unit_test.go b/internal/structs/structs_z_unit_test.go index 377f1538b..1668e8b10 100644 --- a/internal/structs/structs_z_unit_test.go +++ b/internal/structs/structs_z_unit_test.go @@ -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) { diff --git a/util/gvalid/gvalid_validator_rule_required.go b/util/gvalid/gvalid_validator_rule_required.go index e50be29e0..14470f96d 100644 --- a/util/gvalid/gvalid_validator_rule_required.go +++ b/util/gvalid/gvalid_validator_rule_required.go @@ -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