feat: conf inherit (#2568)
* feat: add ValuerWithParent * feat: make etcd config inherit from parents * chore: add more tests * chore: add more tests * chore: add more comments * chore: refactor * chore: add more comments * fix: fix duplicated code and refactor * fix: remove unnecessary code * fix: fix test case for removing print * feat: support partial inherit
This commit is contained in:
@@ -8,6 +8,7 @@ type (
|
|||||||
// use context and OptionalDep option to determine the value of Optional
|
// use context and OptionalDep option to determine the value of Optional
|
||||||
// nothing to do with context.Context
|
// nothing to do with context.Context
|
||||||
fieldOptionsWithContext struct {
|
fieldOptionsWithContext struct {
|
||||||
|
Inherit bool
|
||||||
FromString bool
|
FromString bool
|
||||||
Optional bool
|
Optional bool
|
||||||
Options []string
|
Options []string
|
||||||
@@ -40,6 +41,10 @@ func (o *fieldOptionsWithContext) getDefault() (string, bool) {
|
|||||||
return o.Default, len(o.Default) > 0
|
return o.Default, len(o.Default) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *fieldOptionsWithContext) inherit() bool {
|
||||||
|
return o != nil && o.Inherit
|
||||||
|
}
|
||||||
|
|
||||||
func (o *fieldOptionsWithContext) optional() bool {
|
func (o *fieldOptionsWithContext) optional() bool {
|
||||||
return o != nil && o.Optional
|
return o != nil && o.Optional
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,15 +70,15 @@ func UnmarshalKey(m map[string]interface{}, v interface{}) error {
|
|||||||
|
|
||||||
// Unmarshal unmarshals m into v.
|
// Unmarshal unmarshals m into v.
|
||||||
func (u *Unmarshaler) Unmarshal(m map[string]interface{}, v interface{}) error {
|
func (u *Unmarshaler) Unmarshal(m map[string]interface{}, v interface{}) error {
|
||||||
return u.UnmarshalValuer(MapValuer(m), v)
|
return u.UnmarshalValuer(mapValuer(m), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalValuer unmarshals m into v.
|
// UnmarshalValuer unmarshals m into v.
|
||||||
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v interface{}) error {
|
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v interface{}) error {
|
||||||
return u.unmarshalWithFullName(m, v, "")
|
return u.unmarshalWithFullName(simpleValuer{current: m}, v, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) unmarshalWithFullName(m Valuer, v interface{}, fullName string) error {
|
func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v interface{}, fullName string) error {
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
if err := ValidatePtr(&rv); err != nil {
|
if err := ValidatePtr(&rv); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -102,7 +102,7 @@ func (u *Unmarshaler) unmarshalWithFullName(m Valuer, v interface{}, fullName st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value reflect.Value,
|
||||||
m Valuer, fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
key, options, err := u.parseOptionsWithContext(field, m, fullName)
|
key, options, err := u.parseOptionsWithContext(field, m, fullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -120,7 +120,7 @@ func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value ref
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, value reflect.Value,
|
||||||
key string, m Valuer, fullName string) error {
|
key string, m valuerWithParent, fullName string) error {
|
||||||
var filled bool
|
var filled bool
|
||||||
var required int
|
var required int
|
||||||
var requiredFilled int
|
var requiredFilled int
|
||||||
@@ -161,7 +161,7 @@ func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, v
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, value reflect.Value,
|
||||||
m Valuer, fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
maybeNewValue(field, value)
|
maybeNewValue(field, value)
|
||||||
fieldType := Deref(field.Type)
|
fieldType := Deref(field.Type)
|
||||||
indirectValue := reflect.Indirect(value)
|
indirectValue := reflect.Indirect(value)
|
||||||
@@ -175,8 +175,8 @@ func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, v
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value, m Valuer,
|
func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Value,
|
||||||
fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
if usingDifferentKeys(u.key, field) {
|
if usingDifferentKeys(u.key, field) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -189,15 +189,23 @@ func (u *Unmarshaler) processField(field reflect.StructField, value reflect.Valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processFieldNotFromString(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processFieldNotFromString(field reflect.StructField, value reflect.Value,
|
||||||
mapValue interface{}, opts *fieldOptionsWithContext, fullName string) error {
|
vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error {
|
||||||
fieldType := field.Type
|
fieldType := field.Type
|
||||||
derefedFieldType := Deref(fieldType)
|
derefedFieldType := Deref(fieldType)
|
||||||
typeKind := derefedFieldType.Kind()
|
typeKind := derefedFieldType.Kind()
|
||||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
valueKind := reflect.TypeOf(vp.value).Kind()
|
||||||
|
mapValue := vp.value
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
||||||
return u.processFieldStruct(field, value, mapValue, fullName)
|
if mv, ok := mapValue.(map[string]interface{}); ok {
|
||||||
|
return u.processFieldStruct(field, value, &simpleValuer{
|
||||||
|
current: mapValuer(mv),
|
||||||
|
parent: vp.parent,
|
||||||
|
}, fullName)
|
||||||
|
} else {
|
||||||
|
return errTypeMismatch
|
||||||
|
}
|
||||||
case valueKind == reflect.Map && typeKind == reflect.Map:
|
case valueKind == reflect.Map && typeKind == reflect.Map:
|
||||||
return u.fillMap(field, value, mapValue)
|
return u.fillMap(field, value, mapValue)
|
||||||
case valueKind == reflect.String && typeKind == reflect.Map:
|
case valueKind == reflect.String && typeKind == reflect.Map:
|
||||||
@@ -292,18 +300,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(field reflect.StructFi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processFieldStruct(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processFieldStruct(field reflect.StructField, value reflect.Value,
|
||||||
mapValue interface{}, fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
convertedValue, ok := mapValue.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
|
||||||
return fmt.Errorf("error: field: %s, expect map[string]interface{}, actual %v", fullName, valueKind)
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.processFieldStructWithMap(field, value, MapValuer(convertedValue), fullName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unmarshaler) processFieldStructWithMap(field reflect.StructField, value reflect.Value,
|
|
||||||
m Valuer, fullName string) error {
|
|
||||||
if field.Type.Kind() == reflect.Ptr {
|
if field.Type.Kind() == reflect.Ptr {
|
||||||
baseType := Deref(field.Type)
|
baseType := Deref(field.Type)
|
||||||
target := reflect.New(baseType).Elem()
|
target := reflect.New(baseType).Elem()
|
||||||
@@ -342,7 +339,7 @@ func (u *Unmarshaler) processFieldTextUnmarshaler(field reflect.StructField, val
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
|
||||||
m Valuer, fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -353,16 +350,22 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
|||||||
if u.opts.canonicalKey != nil {
|
if u.opts.canonicalKey != nil {
|
||||||
canonicalKey = u.opts.canonicalKey(key)
|
canonicalKey = u.opts.canonicalKey(key)
|
||||||
}
|
}
|
||||||
mapValue, hasValue := getValue(m, canonicalKey)
|
|
||||||
if hasValue {
|
valuer := createValuer(m, opts)
|
||||||
return u.processNamedFieldWithValue(field, value, mapValue, key, opts, fullName)
|
mapValue, hasValue := getValue(valuer, canonicalKey)
|
||||||
|
if !hasValue {
|
||||||
|
return u.processNamedFieldWithoutValue(field, value, opts, fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.processNamedFieldWithoutValue(field, value, opts, fullName)
|
return u.processNamedFieldWithValue(field, value, valueWithParent{
|
||||||
|
value: mapValue,
|
||||||
|
parent: valuer,
|
||||||
|
}, key, opts, fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, value reflect.Value,
|
func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, value reflect.Value,
|
||||||
mapValue interface{}, key string, opts *fieldOptionsWithContext, fullName string) error {
|
vp valueWithParent, key string, opts *fieldOptionsWithContext, fullName string) error {
|
||||||
|
mapValue := vp.value
|
||||||
if mapValue == nil {
|
if mapValue == nil {
|
||||||
if opts.optional() {
|
if opts.optional() {
|
||||||
return nil
|
return nil
|
||||||
@@ -384,7 +387,7 @@ func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, valu
|
|||||||
fieldKind := Deref(field.Type).Kind()
|
fieldKind := Deref(field.Type).Kind()
|
||||||
switch fieldKind {
|
switch fieldKind {
|
||||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
|
||||||
return u.processFieldNotFromString(field, value, mapValue, opts, fullName)
|
return u.processFieldNotFromString(field, value, vp, opts, fullName)
|
||||||
default:
|
default:
|
||||||
if u.opts.fromString || opts.fromString() {
|
if u.opts.fromString || opts.fromString() {
|
||||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||||
@@ -403,7 +406,7 @@ func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, valu
|
|||||||
return fillPrimitive(field.Type, value, mapValue, opts, fullName)
|
return fillPrimitive(field.Type, value, mapValue, opts, fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.processFieldNotFromString(field, value, mapValue, opts, fullName)
|
return u.processFieldNotFromString(field, value, vp, opts, fullName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +434,9 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(field reflect.StructField, v
|
|||||||
switch fieldKind {
|
switch fieldKind {
|
||||||
case reflect.Array, reflect.Map, reflect.Slice:
|
case reflect.Array, reflect.Map, reflect.Slice:
|
||||||
if !opts.optional() {
|
if !opts.optional() {
|
||||||
return u.processFieldNotFromString(field, value, emptyMap, opts, fullName)
|
return u.processFieldNotFromString(field, value, valueWithParent{
|
||||||
|
value: emptyMap,
|
||||||
|
}, opts, fullName)
|
||||||
}
|
}
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
if !opts.optional() {
|
if !opts.optional() {
|
||||||
@@ -439,10 +444,14 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(field reflect.StructField, v
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if required {
|
if required {
|
||||||
return fmt.Errorf("%q is not set", fullName)
|
return fmt.Errorf("%q is not set", fullName)
|
||||||
}
|
}
|
||||||
return u.processFieldNotFromString(field, value, emptyMap, opts, fullName)
|
|
||||||
|
return u.processFieldNotFromString(field, value, valueWithParent{
|
||||||
|
value: emptyMap,
|
||||||
|
}, opts, fullName)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if !opts.optional() {
|
if !opts.optional() {
|
||||||
@@ -738,6 +747,20 @@ func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent {
|
||||||
|
if opts.inherit() {
|
||||||
|
return recursiveValuer{
|
||||||
|
current: v,
|
||||||
|
parent: v.Parent(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return simpleValuer{
|
||||||
|
current: v,
|
||||||
|
parent: v.Parent(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fillDurationValue(fieldKind reflect.Kind, value reflect.Value, dur string) error {
|
func fillDurationValue(fieldKind reflect.Kind, value reflect.Value, dur string) error {
|
||||||
d, err := time.ParseDuration(dur)
|
d, err := time.ParseDuration(dur)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -805,26 +828,30 @@ func fillWithSameType(field reflect.StructField, value reflect.Value, mapValue i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey
|
// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey
|
||||||
func getValue(m Valuer, key string) (interface{}, bool) {
|
func getValue(m valuerWithParent, key string) (interface{}, bool) {
|
||||||
keys := readKeys(key)
|
keys := readKeys(key)
|
||||||
return getValueWithChainedKeys(m, keys)
|
return getValueWithChainedKeys(m, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getValueWithChainedKeys(m Valuer, keys []string) (interface{}, bool) {
|
func getValueWithChainedKeys(m valuerWithParent, keys []string) (interface{}, bool) {
|
||||||
if len(keys) == 1 {
|
switch len(keys) {
|
||||||
|
case 0:
|
||||||
|
return nil, false
|
||||||
|
case 1:
|
||||||
v, ok := m.Value(keys[0])
|
v, ok := m.Value(keys[0])
|
||||||
return v, ok
|
return v, ok
|
||||||
}
|
default:
|
||||||
|
|
||||||
if len(keys) > 1 {
|
|
||||||
if v, ok := m.Value(keys[0]); ok {
|
if v, ok := m.Value(keys[0]); ok {
|
||||||
if nextm, ok := v.(map[string]interface{}); ok {
|
if nextm, ok := v.(map[string]interface{}); ok {
|
||||||
return getValueWithChainedKeys(MapValuer(nextm), keys[1:])
|
return getValueWithChainedKeys(recursiveValuer{
|
||||||
|
current: mapValuer(nextm),
|
||||||
|
parent: m,
|
||||||
|
}, keys[1:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func join(elem ...string) string {
|
func join(elem ...string) string {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stringx"
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,6 +38,29 @@ func TestUnmarshalWithoutTagName(t *testing.T) {
|
|||||||
assert.True(t, in.Optional)
|
assert.True(t, in.Optional)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalWithoutTagNameWithCanonicalKey(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
}
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"Name": "go-zero",
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
unmarshaler := NewUnmarshaler(defaultKeyName, WithCanonicalKeyFunc(func(s string) string {
|
||||||
|
first := true
|
||||||
|
return strings.Map(func(r rune) rune {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
return unicode.ToTitle(r)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, s)
|
||||||
|
}))
|
||||||
|
assert.Nil(t, unmarshaler.Unmarshal(m, &in))
|
||||||
|
assert.Equal(t, "go-zero", in.Name)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalBool(t *testing.T) {
|
func TestUnmarshalBool(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
True bool `key:"yes"`
|
True bool `key:"yes"`
|
||||||
@@ -2718,6 +2741,256 @@ func TestUnmarshalNestedMapSimpleTypeMatch(t *testing.T) {
|
|||||||
assert.Equal(t, "1", c.Anything["id"])
|
assert.Equal(t, "1", c.Anything["id"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritPrimitiveUseParent(t *testing.T) {
|
||||||
|
type (
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery string `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Discovery string `key:"discovery"`
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NoError(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"discovery": "localhost:8080",
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
assert.Equal(t, "localhost:8080", s.Discovery)
|
||||||
|
assert.Equal(t, "localhost:8080", s.Component.Discovery)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritPrimitiveUseSelf(t *testing.T) {
|
||||||
|
type (
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery string `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Discovery string `key:"discovery"`
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NoError(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"discovery": "localhost:8080",
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"discovery": "localhost:8888",
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
assert.Equal(t, "localhost:8080", s.Discovery)
|
||||||
|
assert.Equal(t, "localhost:8888", s.Component.Discovery)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritPrimitiveNotExist(t *testing.T) {
|
||||||
|
type (
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery string `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NotNil(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritStructUseParent(t *testing.T) {
|
||||||
|
type (
|
||||||
|
discovery struct {
|
||||||
|
Host string `key:"host"`
|
||||||
|
Port int `key:"port"`
|
||||||
|
}
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery discovery `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Discovery discovery `key:"discovery"`
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NoError(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"discovery": map[string]interface{}{
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080,
|
||||||
|
},
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
assert.Equal(t, "localhost", s.Discovery.Host)
|
||||||
|
assert.Equal(t, 8080, s.Discovery.Port)
|
||||||
|
assert.Equal(t, "localhost", s.Component.Discovery.Host)
|
||||||
|
assert.Equal(t, 8080, s.Component.Discovery.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritStructUseSelf(t *testing.T) {
|
||||||
|
type (
|
||||||
|
discovery struct {
|
||||||
|
Host string `key:"host"`
|
||||||
|
Port int `key:"port"`
|
||||||
|
}
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery discovery `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Discovery discovery `key:"discovery"`
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NoError(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"discovery": map[string]interface{}{
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080,
|
||||||
|
},
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"discovery": map[string]interface{}{
|
||||||
|
"host": "remotehost",
|
||||||
|
"port": 8888,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
assert.Equal(t, "localhost", s.Discovery.Host)
|
||||||
|
assert.Equal(t, 8080, s.Discovery.Port)
|
||||||
|
assert.Equal(t, "remotehost", s.Component.Discovery.Host)
|
||||||
|
assert.Equal(t, 8888, s.Component.Discovery.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritStructNotExist(t *testing.T) {
|
||||||
|
type (
|
||||||
|
discovery struct {
|
||||||
|
Host string `key:"host"`
|
||||||
|
Port int `key:"port"`
|
||||||
|
}
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery discovery `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NotNil(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritStructUsePartial(t *testing.T) {
|
||||||
|
type (
|
||||||
|
discovery struct {
|
||||||
|
Host string `key:"host"`
|
||||||
|
Port int `key:"port"`
|
||||||
|
}
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery discovery `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Discovery discovery `key:"discovery"`
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NoError(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"discovery": map[string]interface{}{
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080,
|
||||||
|
},
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"discovery": map[string]interface{}{
|
||||||
|
"port": 8888,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
assert.Equal(t, "localhost", s.Discovery.Host)
|
||||||
|
assert.Equal(t, 8080, s.Discovery.Port)
|
||||||
|
assert.Equal(t, "localhost", s.Component.Discovery.Host)
|
||||||
|
assert.Equal(t, 8888, s.Component.Discovery.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInheritStructUseSelfIncorrectType(t *testing.T) {
|
||||||
|
type (
|
||||||
|
discovery struct {
|
||||||
|
Host string `key:"host"`
|
||||||
|
Port int `key:"port"`
|
||||||
|
}
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery discovery `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Discovery discovery `key:"discovery"`
|
||||||
|
Component component `key:"component"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NotNil(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"discovery": map[string]interface{}{
|
||||||
|
"host": "localhost",
|
||||||
|
},
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"discovery": map[string]string{
|
||||||
|
"host": "remotehost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshaler_InheritFromGrandparent(t *testing.T) {
|
||||||
|
type (
|
||||||
|
component struct {
|
||||||
|
Name string `key:"name"`
|
||||||
|
Discovery string `key:"discovery,inherit"`
|
||||||
|
}
|
||||||
|
middle struct {
|
||||||
|
Value component `key:"value"`
|
||||||
|
}
|
||||||
|
server struct {
|
||||||
|
Discovery string `key:"discovery"`
|
||||||
|
Middle middle `key:"middle"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var s server
|
||||||
|
assert.NoError(t, UnmarshalKey(map[string]interface{}{
|
||||||
|
"discovery": "localhost:8080",
|
||||||
|
"middle": map[string]interface{}{
|
||||||
|
"value": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, &s))
|
||||||
|
assert.Equal(t, "localhost:8080", s.Discovery)
|
||||||
|
assert.Equal(t, "localhost:8080", s.Middle.Value.Discovery)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalValuer(t *testing.T) {
|
func TestUnmarshalValuer(t *testing.T) {
|
||||||
unmarshaler := NewUnmarshaler(jsonTagKey)
|
unmarshaler := NewUnmarshaler(jsonTagKey)
|
||||||
var foo string
|
var foo string
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
defaultOption = "default"
|
defaultOption = "default"
|
||||||
|
inheritOption = "inherit"
|
||||||
stringOption = "string"
|
stringOption = "string"
|
||||||
optionalOption = "optional"
|
optionalOption = "optional"
|
||||||
optionsOption = "options"
|
optionsOption = "options"
|
||||||
@@ -335,6 +336,8 @@ func parseNumberRange(str string) (*numberRange, error) {
|
|||||||
|
|
||||||
func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||||
switch {
|
switch {
|
||||||
|
case option == inheritOption:
|
||||||
|
fieldOpts.Inherit = true
|
||||||
case option == stringOption:
|
case option == stringOption:
|
||||||
fieldOpts.FromString = true
|
fieldOpts.FromString = true
|
||||||
case strings.HasPrefix(option, optionalOption):
|
case strings.HasPrefix(option, optionalOption):
|
||||||
|
|||||||
@@ -7,12 +7,94 @@ type (
|
|||||||
Value(key string) (interface{}, bool)
|
Value(key string) (interface{}, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A MapValuer is a map that can use Value method to get values with given keys.
|
// A valuerWithParent defines a node that has a parent node.
|
||||||
MapValuer map[string]interface{}
|
valuerWithParent interface {
|
||||||
|
Valuer
|
||||||
|
// Parent get the parent valuer for current node.
|
||||||
|
Parent() valuerWithParent
|
||||||
|
}
|
||||||
|
|
||||||
|
// A node is a map that can use Value method to get values with given keys.
|
||||||
|
node struct {
|
||||||
|
current Valuer
|
||||||
|
parent valuerWithParent
|
||||||
|
}
|
||||||
|
|
||||||
|
// A valueWithParent is used to wrap the value with its parent.
|
||||||
|
valueWithParent struct {
|
||||||
|
value interface{}
|
||||||
|
parent valuerWithParent
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapValuer is a type for map to meet the Valuer interface.
|
||||||
|
mapValuer map[string]interface{}
|
||||||
|
// simpleValuer is a type to get value from current node.
|
||||||
|
simpleValuer node
|
||||||
|
// recursiveValuer is a type to get the value recursively from current and parent nodes.
|
||||||
|
recursiveValuer node
|
||||||
)
|
)
|
||||||
|
|
||||||
// Value gets the value associated with the given key from mv.
|
// Value gets the value assciated with the given key from mv.
|
||||||
func (mv MapValuer) Value(key string) (interface{}, bool) {
|
func (mv mapValuer) Value(key string) (interface{}, bool) {
|
||||||
v, ok := mv[key]
|
v, ok := mv[key]
|
||||||
return v, ok
|
return v, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value gets the value associated with the given key from sv.
|
||||||
|
func (sv simpleValuer) Value(key string) (interface{}, bool) {
|
||||||
|
v, ok := sv.current.Value(key)
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent get the parent valuer from sv.
|
||||||
|
func (sv simpleValuer) Parent() valuerWithParent {
|
||||||
|
if sv.parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return recursiveValuer{
|
||||||
|
current: sv.parent,
|
||||||
|
parent: sv.parent.Parent(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value gets the value associated with the given key from rv,
|
||||||
|
// and it will inherit the value from parent nodes.
|
||||||
|
func (rv recursiveValuer) Value(key string) (interface{}, bool) {
|
||||||
|
val, ok := rv.current.Value(key)
|
||||||
|
if !ok {
|
||||||
|
if parent := rv.Parent(); parent != nil {
|
||||||
|
return parent.Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if vm, ok := val.(map[string]interface{}); ok {
|
||||||
|
if parent := rv.Parent(); parent != nil {
|
||||||
|
pv, pok := parent.Value(key)
|
||||||
|
if pok {
|
||||||
|
if pm, ok := pv.(map[string]interface{}); ok {
|
||||||
|
for k, v := range vm {
|
||||||
|
pm[k] = v
|
||||||
|
}
|
||||||
|
return pm, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent get the parent valuer from rv.
|
||||||
|
func (rv recursiveValuer) Parent() valuerWithParent {
|
||||||
|
if rv.parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return recursiveValuer{
|
||||||
|
current: rv.parent,
|
||||||
|
parent: rv.parent.Parent(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
33
core/mapping/valuer_test.go
Normal file
33
core/mapping/valuer_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package mapping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMapValuerWithInherit_Value(t *testing.T) {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"discovery": map[string]interface{}{
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080,
|
||||||
|
},
|
||||||
|
"component": map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
valuer := recursiveValuer{
|
||||||
|
current: mapValuer(input["component"].(map[string]interface{})),
|
||||||
|
parent: simpleValuer{
|
||||||
|
current: mapValuer(input),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := valuer.Value("discovery")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
m, ok := val.(map[string]interface{})
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, "localhost", m["host"])
|
||||||
|
assert.Equal(t, 8080, m["port"])
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ type (
|
|||||||
RpcServerConf struct {
|
RpcServerConf struct {
|
||||||
service.ServiceConf
|
service.ServiceConf
|
||||||
ListenOn string
|
ListenOn string
|
||||||
Etcd discov.EtcdConf `json:",optional"`
|
Etcd discov.EtcdConf `json:",optional,inherit"`
|
||||||
Auth bool `json:",optional"`
|
Auth bool `json:",optional"`
|
||||||
Redis redis.RedisKeyConf `json:",optional"`
|
Redis redis.RedisKeyConf `json:",optional"`
|
||||||
StrictControl bool `json:",optional"`
|
StrictControl bool `json:",optional"`
|
||||||
@@ -25,7 +25,7 @@ type (
|
|||||||
|
|
||||||
// A RpcClientConf is a rpc client config.
|
// A RpcClientConf is a rpc client config.
|
||||||
RpcClientConf struct {
|
RpcClientConf struct {
|
||||||
Etcd discov.EtcdConf `json:",optional"`
|
Etcd discov.EtcdConf `json:",optional,inherit"`
|
||||||
Endpoints []string `json:",optional"`
|
Endpoints []string `json:",optional"`
|
||||||
Target string `json:",optional"`
|
Target string `json:",optional"`
|
||||||
App string `json:",optional"`
|
App string `json:",optional"`
|
||||||
|
|||||||
Reference in New Issue
Block a user