Repository: zeromicro/go-zero
Pull request #2820 (2023-01-24 11:35:39)

fix: problem on name overlaping in config

codecov-commenter: # [Codecov](https://codecov.io/gh/zeromicro/go-zero/pull/2820?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#2820](https://codecov.io/gh/zeromicro/go-zero/pull/2820?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (3f5c639) into [master](https://codecov.io/gh/zeromicro/go-zero/commit/eab904af64109dc94c989383a12325e70f9f3ac0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (eab904a) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.


| [Impacted Files](https://codecov.io/gh/zeromicro/go-zero/pull/2820?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [core/conf/config.go](https://codecov.io/gh/zeromicro/go-zero/pull/2820?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9jb25mL2NvbmZpZy5nbw==) | `93.02% <100.00%> (+0.28%)` | :arrow_up: |


Patch:
diff --git a/core/conf/config.go b/core/conf/config.go
index b64b9258..062e1b8f 100644
--- a/core/conf/config.go
+++ b/core/conf/config.go
@@ -22,7 +22,6 @@ var loaders = map[string]func([]byte, any) error{
 
 type fieldInfo struct {
 	name     string
-	kind     reflect.Kind
 	children map[string]fieldInfo
 }
 
@@ -140,7 +139,6 @@ func buildStructFieldsInfo(tp reflect.Type) map[string]fieldInfo {
 			} else {
 				info[lowerCaseName] = fieldInfo{
 					name: name,
-					kind: ft.Kind(),
 				}
 			}
 			continue
@@ -156,10 +154,16 @@ func buildStructFieldsInfo(tp reflect.Type) map[string]fieldInfo {
 			fields = buildFieldsInfo(ft.Elem())
 		}
 
-		info[lowerCaseName] = fieldInfo{
-			name:     name,
-			kind:     ft.Kind(),
-			children: fields,
+		if prev, ok := info[lowerCaseName]; ok {
+			// merge fields
+			for k, v := range fields {
+				prev.children[k] = v
+			}
+		} else {
+			info[lowerCaseName] = fieldInfo{
+				name:     name,
+				children: fields,
+			}
 		}
 	}
 
diff --git a/core/conf/readme.md b/core/conf/readme.md
index cad39198..7bd58540 100644
--- a/core/conf/readme.md
+++ b/core/conf/readme.md
@@ -4,6 +4,7 @@
 
 ```go
 type RestfulConf struct {
+  ServiceName  string        `json:",env=SERVICE_NAME"`  // read from env automatically
 	Host         string        `json:",default=0.0.0.0"`
 	Port         int
 	LogMode      string        `json:",options=[file,console]"`
@@ -21,20 +22,20 @@ type RestfulConf struct {
 
 ```yaml
 # most fields are optional or have default values
-Port: 8080
-LogMode: console
+port: 8080
+logMode: console
 # you can use env settings
-MaxBytes: ${MAX_BYTES}
+maxBytes: ${MAX_BYTES}
 ```
 
 - toml example
 
 ```toml
 # most fields are optional or have default values
-Port = 8_080
-LogMode = "console"
+port = 8_080
+logMode = "console"
 # you can use env settings
-MaxBytes = "${MAX_BYTES}"
+maxBytes = "${MAX_BYTES}"
 ```
 
 3. Load the config from a file:


Test patch:
diff --git a/core/conf/config_test.go b/core/conf/config_test.go
index 7be4f540..3465c729 100644
--- a/core/conf/config_test.go
+++ b/core/conf/config_test.go
@@ -384,6 +384,78 @@ func TestLoadFromYamlBytesLayers(t *testing.T) {
 	assert.Equal(t, "foo", val.Value)
 }
 
+func TestLoadFromYamlItemOverlay(t *testing.T) {
+	type (
+		Redis struct {
+			Host string
+			Port int
+		}
+
+		RedisKey struct {
+			Redis
+			Key string
+		}
+
+		Server struct {
+			Redis RedisKey
+		}
+
+		TestConfig struct {
+			Server
+			Redis Redis
+		}
+	)
+
+	input := []byte(`Redis:
+  Host: localhost
+  Port: 6379
+  Key: test
+`)
+
+	var c TestConfig
+	if assert.NoError(t, LoadFromYamlBytes(input, &c)) {
+		assert.Equal(t, "localhost", c.Redis.Host)
+		assert.Equal(t, 6379, c.Redis.Port)
+		assert.Equal(t, "test", c.Server.Redis.Key)
+	}
+}
+
+func TestLoadFromYamlItemOverlayWithMap(t *testing.T) {
+	type (
+		Redis struct {
+			Host string
+			Port int
+		}
+
+		RedisKey struct {
+			Redis
+			Key string
+		}
+
+		Server struct {
+			Redis RedisKey
+		}
+
+		TestConfig struct {
+			Server
+			Redis map[string]interface{}
+		}
+	)
+
+	input := []byte(`Redis:
+  Host: localhost
+  Port: 6379
+  Key: test
+`)
+
+	var c TestConfig
+	if assert.NoError(t, LoadFromYamlBytes(input, &c)) {
+		assert.Equal(t, "localhost", c.Server.Redis.Host)
+		assert.Equal(t, 6379, c.Server.Redis.Port)
+		assert.Equal(t, "test", c.Server.Redis.Key)
+	}
+}
+
 func TestUnmarshalJsonBytesMap(t *testing.T) {
 	input := []byte(`{"foo":{"/mtproto.RPCTos": "bff.bff","bar":"baz"}}`)