From 1ef79f0e681ed34cf06545115d29f127442fd457 Mon Sep 17 00:00:00 2001 From: "aiden.ma" Date: Thu, 3 Oct 2024 15:38:31 +0800 Subject: [PATCH 1/2] feat/conf_recursion --- core/conf/config.go | 71 +++++++++++++++++++++++++++------------- core/conf/config_test.go | 49 ++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/core/conf/config.go b/core/conf/config.go index d3795c0a494b..11ccec7ad065 100644 --- a/core/conf/config.go +++ b/core/conf/config.go @@ -73,7 +73,7 @@ func LoadConfig(file string, v any, opts ...Option) error { // LoadFromJsonBytes loads config into v from content json bytes. func LoadFromJsonBytes(content []byte, v any) error { - info, err := buildFieldsInfo(reflect.TypeOf(v), "") + info, err := buildFieldsInfo(reflect.TypeOf(v), "", make(fieldCache)) if err != nil { return err } @@ -143,10 +143,11 @@ func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName st return nil } -func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error { +func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string, + cache fieldCache) error { switch ft.Kind() { case reflect.Struct: - fields, err := buildFieldsInfo(ft, fullName) + fields, err := buildFieldsInfo(ft, fullName, cache) if err != nil { return err } @@ -157,7 +158,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T } } case reflect.Map: - elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName) + elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName, cache) if err != nil { return err } @@ -183,14 +184,18 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T return nil } -func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) { +func buildFieldsInfo(tp reflect.Type, fullName string, cache fieldCache) (*fieldInfo, error) { tp = mapping.Deref(tp) + if info, ok := cache[tp]; ok { + return info, nil + } + switch tp.Kind() { case reflect.Struct: - return buildStructFieldsInfo(tp, fullName) + return buildStructFieldsInfo(tp, fullName, cache) case reflect.Array, reflect.Slice: - return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName) + return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName, cache) case reflect.Chan, reflect.Func: return nil, fmt.Errorf("unsupported type: %s", tp.Kind()) default: @@ -200,33 +205,32 @@ func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) { } } -func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error { +func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string, + cache fieldCache) error { var finfo *fieldInfo var err error switch ft.Kind() { case reflect.Struct: - finfo, err = buildFieldsInfo(ft, fullName) + finfo, err = buildFieldsInfo(ft, fullName, cache) if err != nil { return err } case reflect.Array, reflect.Slice: - finfo, err = buildFieldsInfo(ft.Elem(), fullName) + finfo, err = buildFieldsInfo(ft.Elem(), fullName, cache) if err != nil { return err } case reflect.Map: - elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName) + elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName, cache) if err != nil { return err } - finfo = &fieldInfo{ - children: make(map[string]*fieldInfo), - mapField: elemInfo, - } + finfo = cache.get(mapping.Deref(ft.Elem())) + finfo.mapField = elemInfo default: - finfo, err = buildFieldsInfo(ft, fullName) + finfo, err = buildFieldsInfo(ft, fullName, cache) if err != nil { return err } @@ -235,10 +239,8 @@ func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, return addOrMergeFields(info, lowerCaseName, finfo, fullName) } -func buildStructFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) { - info := &fieldInfo{ - children: make(map[string]*fieldInfo), - } +func buildStructFieldsInfo(tp reflect.Type, fullName string, cache fieldCache) (*fieldInfo, error) { + info := cache.get(tp) for i := 0; i < tp.NumField(); i++ { field := tp.Field(i) @@ -252,11 +254,11 @@ func buildStructFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) // flatten anonymous fields if field.Anonymous { if err := buildAnonymousFieldInfo(info, lowerCaseName, ft, - getFullName(fullName, lowerCaseName)); err != nil { + getFullName(fullName, lowerCaseName), cache); err != nil { return nil, err } } else if err := buildNamedFieldInfo(info, lowerCaseName, ft, - getFullName(fullName, lowerCaseName)); err != nil { + getFullName(fullName, lowerCaseName), cache); err != nil { return nil, err } } @@ -359,3 +361,28 @@ func getFullName(parent, child string) string { return strings.Join([]string{parent, child}, ".") } + +type fieldCache map[reflect.Type]*fieldInfo + +func (c fieldCache) get(tp reflect.Type) *fieldInfo { + tp = mapping.Deref(tp) + switch tp.Kind() { + case reflect.Struct: + case reflect.Array, reflect.Slice: + tp = mapping.Deref(tp.Elem()) + default: + return &fieldInfo{ + children: make(map[string]*fieldInfo), + } + } + + info, ok := c[tp] + if !ok { + info = &fieldInfo{ + children: make(map[string]*fieldInfo), + } + c[tp] = info + } + + return info +} diff --git a/core/conf/config_test.go b/core/conf/config_test.go index 7735e61921d7..b5ddde8c5eb8 100644 --- a/core/conf/config_test.go +++ b/core/conf/config_test.go @@ -1194,6 +1194,53 @@ Email = "bar"`) }) } +func Test_FieldRecursion(t *testing.T) { + t.Run("map recursion", func(t *testing.T) { + type Inner struct { + Name string + Children map[string]Inner + } + + type Config struct { + Inner Inner + } + + input := []byte(`{"Inner": {"Name": "foo"}}`) + var c Config + assert.NoError(t, LoadFromJsonBytes(input, &c)) + }) + + t.Run("array recursion", func(t *testing.T) { + type Inner struct { + Name string + Children map[string]Inner + } + + type Config struct { + Inners []Inner + } + + input := []byte(`{"Inners": [{"Name": "foo"}]}`) + var c Config + assert.NoError(t, LoadFromJsonBytes(input, &c)) + }) + + t.Run("map anonymous recursion", func(t *testing.T) { + type Inner struct { + Name string + Children map[string]Inner + } + + type Config struct { + Inner + } + + input := []byte(`{"Name": "foo"}`) + var c Config + assert.NoError(t, LoadFromJsonBytes(input, &c)) + }) +} + func Test_getFullName(t *testing.T) { assert.Equal(t, "a.b", getFullName("a", "b")) assert.Equal(t, "a", getFullName("", "a")) @@ -1277,7 +1324,7 @@ func Test_buildFieldsInfo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := buildFieldsInfo(tt.t, "") + _, err := buildFieldsInfo(tt.t, "", make(fieldCache)) if tt.ok { assert.NoError(t, err) } else { From b9abed79b482a371271fa6305b79a199cf7d4604 Mon Sep 17 00:00:00 2001 From: "aiden.ma" Date: Sun, 6 Oct 2024 15:56:18 +0800 Subject: [PATCH 2/2] merge master --- core/conf/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/conf/config.go b/core/conf/config.go index 3cdb08210bad..59de8c0886be 100644 --- a/core/conf/config.go +++ b/core/conf/config.go @@ -186,7 +186,6 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T func buildFieldsInfo(tp reflect.Type, fullName string, cache fieldCache) (*fieldInfo, error) { tp = mapping.Deref(tp) - if info, ok := cache[tp]; ok { return info, nil }