feat: inheritance rewrite error prompt is more friendly (#3156)

This commit is contained in:
MarkJoyMa 2023-04-23 20:56:54 +08:00 committed by GitHub
parent 027193dc99
commit d10740f871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 131 additions and 26 deletions

View File

@ -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), "")
if err != nil {
return err
}
@ -127,13 +127,13 @@ func MustLoad(path string, v any, opts ...Option) {
}
}
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo) error {
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName string) error {
if prev, ok := info.children[key]; ok {
if child.mapField != nil {
return newConflictKeyError(key)
return newConflictKeyError(fullName)
}
if err := mergeFields(prev, key, child.children); err != nil {
if err := mergeFields(prev, key, child.children, fullName); err != nil {
return err
}
} else {
@ -143,27 +143,27 @@ func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo) error {
return nil
}
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type) error {
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
switch ft.Kind() {
case reflect.Struct:
fields, err := buildFieldsInfo(ft)
fields, err := buildFieldsInfo(ft, fullName)
if err != nil {
return err
}
for k, v := range fields.children {
if err = addOrMergeFields(info, k, v); err != nil {
if err = addOrMergeFields(info, k, v, fullName); err != nil {
return err
}
}
case reflect.Map:
elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()))
elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
if err != nil {
return err
}
if _, ok := info.children[lowerCaseName]; ok {
return newConflictKeyError(lowerCaseName)
return newConflictKeyError(fullName)
}
info.children[lowerCaseName] = &fieldInfo{
@ -172,7 +172,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
}
default:
if _, ok := info.children[lowerCaseName]; ok {
return newConflictKeyError(lowerCaseName)
return newConflictKeyError(fullName)
}
info.children[lowerCaseName] = &fieldInfo{
@ -183,14 +183,14 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
return nil
}
func buildFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
tp = mapping.Deref(tp)
switch tp.Kind() {
case reflect.Struct:
return buildStructFieldsInfo(tp)
return buildStructFieldsInfo(tp, fullName)
case reflect.Array, reflect.Slice:
return buildFieldsInfo(mapping.Deref(tp.Elem()))
return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName)
case reflect.Chan, reflect.Func:
return nil, fmt.Errorf("unsupported type: %s", tp.Kind())
default:
@ -200,23 +200,23 @@ func buildFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
}
}
func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type) error {
func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
var finfo *fieldInfo
var err error
switch ft.Kind() {
case reflect.Struct:
finfo, err = buildFieldsInfo(ft)
finfo, err = buildFieldsInfo(ft, fullName)
if err != nil {
return err
}
case reflect.Array, reflect.Slice:
finfo, err = buildFieldsInfo(ft.Elem())
finfo, err = buildFieldsInfo(ft.Elem(), fullName)
if err != nil {
return err
}
case reflect.Map:
elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()))
elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
if err != nil {
return err
}
@ -226,16 +226,16 @@ func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type)
mapField: elemInfo,
}
default:
finfo, err = buildFieldsInfo(ft)
finfo, err = buildFieldsInfo(ft, fullName)
if err != nil {
return err
}
}
return addOrMergeFields(info, lowerCaseName, finfo)
return addOrMergeFields(info, lowerCaseName, finfo, fullName)
}
func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
func buildStructFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
info := &fieldInfo{
children: make(map[string]*fieldInfo),
}
@ -251,10 +251,12 @@ func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
ft := mapping.Deref(field.Type)
// flatten anonymous fields
if field.Anonymous {
if err := buildAnonymousFieldInfo(info, lowerCaseName, ft); err != nil {
if err := buildAnonymousFieldInfo(info, lowerCaseName, ft,
getFullName(fullName, lowerCaseName)); err != nil {
return nil, err
}
} else if err := buildNamedFieldInfo(info, lowerCaseName, ft); err != nil {
} else if err := buildNamedFieldInfo(info, lowerCaseName, ft,
getFullName(fullName, lowerCaseName)); err != nil {
return nil, err
}
}
@ -279,15 +281,15 @@ func getTagName(field reflect.StructField) string {
return field.Name
}
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo) error {
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo, fullName string) error {
if len(prev.children) == 0 || len(children) == 0 {
return newConflictKeyError(key)
return newConflictKeyError(fullName)
}
// merge fields
for k, v := range children {
if _, ok := prev.children[k]; ok {
return newConflictKeyError(k)
return newConflictKeyError(fullName)
}
prev.children[k] = v
@ -349,3 +351,11 @@ func newConflictKeyError(key string) conflictKeyError {
func (e conflictKeyError) Error() string {
return fmt.Sprintf("conflict key %s, pay attention to anonymous fields", e.key)
}
func getFullName(parent, child string) string {
if parent == "" {
return child
}
return strings.Join([]string{parent, child}, ".")
}

View File

@ -2,6 +2,7 @@ package conf
import (
"os"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
@ -733,7 +734,7 @@ func Test_FieldOverwrite(t *testing.T) {
input := []byte(`{"Name": "hello"}`)
err := LoadFromJsonBytes(input, val)
assert.ErrorAs(t, err, &dupErr)
assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
assert.Error(t, err)
}
validate(&St0{})
@ -1196,6 +1197,100 @@ Email = "bar"`)
})
}
func Test_getFullName(t *testing.T) {
assert.Equal(t, "a.b", getFullName("a", "b"))
assert.Equal(t, "a", getFullName("", "a"))
}
func Test_buildFieldsInfo(t *testing.T) {
type ParentSt struct {
Name string
M map[string]int
}
tests := []struct {
name string
t reflect.Type
ok bool
containsKey string
}{
{
name: "normal",
t: reflect.TypeOf(struct{ A string }{}),
ok: true,
},
{
name: "struct anonymous",
t: reflect.TypeOf(struct {
ParentSt
Name string
}{}),
ok: false,
containsKey: newConflictKeyError("name").Error(),
},
{
name: "struct ptr anonymous",
t: reflect.TypeOf(struct {
*ParentSt
Name string
}{}),
ok: false,
containsKey: newConflictKeyError("name").Error(),
},
{
name: "more struct anonymous",
t: reflect.TypeOf(struct {
Value struct {
ParentSt
Name string
}
}{}),
ok: false,
containsKey: newConflictKeyError("value.name").Error(),
},
{
name: "map anonymous",
t: reflect.TypeOf(struct {
ParentSt
M string
}{}),
ok: false,
containsKey: newConflictKeyError("m").Error(),
},
{
name: "map more anonymous",
t: reflect.TypeOf(struct {
Value struct {
ParentSt
M string
}
}{}),
ok: false,
containsKey: newConflictKeyError("value.m").Error(),
},
{
name: "struct slice anonymous",
t: reflect.TypeOf([]struct {
ParentSt
Name string
}{}),
ok: false,
containsKey: newConflictKeyError("name").Error(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := buildFieldsInfo(tt.t, "")
if tt.ok {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, err.Error(), tt.containsKey)
}
})
}
}
func createTempFile(ext, text string) (string, error) {
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
if err != nil {