Skip to content

Commit

Permalink
feat: support array in default and options tags (#1386)
Browse files Browse the repository at this point in the history
* feat: support array in default and options tags

* feat: ignore spaces in tags

* test: add more tests
  • Loading branch information
kevwan authored Dec 29, 2021
1 parent 38a36ed commit 23deaf5
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 36 deletions.
73 changes: 46 additions & 27 deletions core/mapping/unmarshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"reflect"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/tal-tech/go-zero/core/jsonx"
Expand All @@ -25,15 +24,17 @@ var (
errValueNotSettable = errors.New("value is not settable")
errValueNotStruct = errors.New("value type is not struct")
keyUnmarshaler = NewUnmarshaler(defaultKeyName)
cacheKeys atomic.Value
cacheKeysLock sync.Mutex
durationType = reflect.TypeOf(time.Duration(0))
cacheKeys map[string][]string
cacheKeysLock sync.Mutex
defaultCache map[string]interface{}
defaultCacheLock sync.Mutex
emptyMap = map[string]interface{}{}
emptyValue = reflect.ValueOf(lang.Placeholder)
)

type (
// A Unmarshaler is used to unmarshal with given tag key.
// Unmarshaler is used to unmarshal with given tag key.
Unmarshaler struct {
key string
opts unmarshalOptions
Expand All @@ -46,12 +47,11 @@ type (
fromString bool
canonicalKey func(key string) string
}

keyCache map[string][]string
)

func init() {
cacheKeys.Store(make(keyCache))
cacheKeys = make(map[string][]string)
defaultCache = make(map[string]interface{})
}

// NewUnmarshaler returns a Unmarshaler.
Expand Down Expand Up @@ -388,7 +388,13 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(field reflect.StructField, v
if derefedType == durationType {
return fillDurationValue(fieldKind, value, defaultValue)
}
return setValue(fieldKind, value, defaultValue)

switch fieldKind {
case reflect.Array, reflect.Slice:
return u.fillSliceWithDefault(derefedType, value, defaultValue)
default:
return setValue(fieldKind, value, defaultValue)
}
}

switch fieldKind {
Expand Down Expand Up @@ -502,7 +508,8 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
return nil
}

func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, baseKind reflect.Kind, value interface{}) error {
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
baseKind reflect.Kind, value interface{}) error {
ithVal := slice.Index(index)
switch v := value.(type) {
case json.Number:
Expand Down Expand Up @@ -531,6 +538,28 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, baseKind re
}
}

func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value reflect.Value,
defaultValue string) error {
baseFieldType := Deref(derefedType.Elem())
baseFieldKind := baseFieldType.Kind()
defaultCacheLock.Lock()
slice, ok := defaultCache[defaultValue]
defaultCacheLock.Unlock()
if !ok {
if baseFieldKind == reflect.String {
slice = parseGroupedSegments(defaultValue)
} else if err := jsonx.UnmarshalFromString(defaultValue, &slice); err != nil {
return err
}

defaultCacheLock.Lock()
defaultCache[defaultValue] = slice
defaultCacheLock.Unlock()
}

return u.fillSlice(derefedType, value, slice)
}

func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue interface{}) (reflect.Value, error) {
mapType := reflect.MapOf(keyType, elemType)
valueType := reflect.TypeOf(mapValue)
Expand Down Expand Up @@ -724,20 +753,6 @@ func getValueWithChainedKeys(m Valuer, keys []string) (interface{}, bool) {
return nil, false
}

func insertKeys(key string, cache []string) {
cacheKeysLock.Lock()
defer cacheKeysLock.Unlock()

keys := cacheKeys.Load().(keyCache)
// copy the contents into the new map, to guarantee the old map is immutable
newKeys := make(keyCache)
for k, v := range keys {
newKeys[k] = v
}
newKeys[key] = cache
cacheKeys.Store(newKeys)
}

func join(elem ...string) string {
var builder strings.Builder

Expand Down Expand Up @@ -768,15 +783,19 @@ func newTypeMismatchError(name string) error {
}

func readKeys(key string) []string {
cache := cacheKeys.Load().(keyCache)
if keys, ok := cache[key]; ok {
cacheKeysLock.Lock()
keys, ok := cacheKeys[key]
cacheKeysLock.Unlock()
if ok {
return keys
}

keys := strings.FieldsFunc(key, func(c rune) bool {
keys = strings.FieldsFunc(key, func(c rune) bool {
return c == delimiter
})
insertKeys(key, keys)
cacheKeysLock.Lock()
cacheKeys[key] = keys
cacheKeysLock.Unlock()

return keys
}
105 changes: 105 additions & 0 deletions core/mapping/unmarshaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,66 @@ func TestUnmarshalIntWithDefault(t *testing.T) {
assert.Equal(t, 1, in.Int)
}

func TestUnmarshalBoolSliceWithDefault(t *testing.T) {
type inner struct {
Bools []bool `key:"bools,default=[true,false]"`
}

var in inner
assert.Nil(t, UnmarshalKey(nil, &in))
assert.ElementsMatch(t, []bool{true, false}, in.Bools)
}

func TestUnmarshalIntSliceWithDefault(t *testing.T) {
type inner struct {
Ints []int `key:"ints,default=[1,2,3]"`
}

var in inner
assert.Nil(t, UnmarshalKey(nil, &in))
assert.ElementsMatch(t, []int{1, 2, 3}, in.Ints)
}

func TestUnmarshalIntSliceWithDefaultHasSpaces(t *testing.T) {
type inner struct {
Ints []int `key:"ints,default=[1, 2, 3]"`
}

var in inner
assert.Nil(t, UnmarshalKey(nil, &in))
assert.ElementsMatch(t, []int{1, 2, 3}, in.Ints)
}

func TestUnmarshalFloatSliceWithDefault(t *testing.T) {
type inner struct {
Floats []float32 `key:"floats,default=[1.1,2.2,3.3]"`
}

var in inner
assert.Nil(t, UnmarshalKey(nil, &in))
assert.ElementsMatch(t, []float32{1.1, 2.2, 3.3}, in.Floats)
}

func TestUnmarshalStringSliceWithDefault(t *testing.T) {
type inner struct {
Strs []string `key:"strs,default=[foo,bar,woo]"`
}

var in inner
assert.Nil(t, UnmarshalKey(nil, &in))
assert.ElementsMatch(t, []string{"foo", "bar", "woo"}, in.Strs)
}

func TestUnmarshalStringSliceWithDefaultHasSpaces(t *testing.T) {
type inner struct {
Strs []string `key:"strs,default=[foo, bar, woo]"`
}

var in inner
assert.Nil(t, UnmarshalKey(nil, &in))
assert.ElementsMatch(t, []string{"foo", "bar", "woo"}, in.Strs)
}

func TestUnmarshalUint(t *testing.T) {
type inner struct {
Uint uint `key:"uint"`
Expand Down Expand Up @@ -861,17 +921,20 @@ func TestUnmarshalSliceOfStruct(t *testing.T) {
func TestUnmarshalWithStringOptionsCorrect(t *testing.T) {
type inner struct {
Value string `key:"value,options=first|second"`
Foo string `key:"foo,options=[bar,baz]"`
Correct string `key:"correct,options=1|2"`
}
m := map[string]interface{}{
"value": "first",
"foo": "bar",
"correct": "2",
}

var in inner
ast := assert.New(t)
ast.Nil(UnmarshalKey(m, &in))
ast.Equal("first", in.Value)
ast.Equal("bar", in.Foo)
ast.Equal("2", in.Correct)
}

Expand Down Expand Up @@ -943,6 +1006,22 @@ func TestUnmarshalStringOptionsWithStringOptionsIncorrect(t *testing.T) {
ast.NotNil(unmarshaler.Unmarshal(m, &in))
}

func TestUnmarshalStringOptionsWithStringOptionsIncorrectGrouped(t *testing.T) {
type inner struct {
Value string `key:"value,options=[first,second]"`
Correct string `key:"correct,options=1|2"`
}
m := map[string]interface{}{
"value": "third",
"correct": "2",
}

var in inner
unmarshaler := NewUnmarshaler(defaultKeyName, WithStringValues())
ast := assert.New(t)
ast.NotNil(unmarshaler.Unmarshal(m, &in))
}

func TestUnmarshalWithStringOptionsIncorrect(t *testing.T) {
type inner struct {
Value string `key:"value,options=first|second"`
Expand Down Expand Up @@ -2518,3 +2597,29 @@ func TestUnmarshalJsonReaderPtrArray(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, 3, len(res.B))
}

func TestUnmarshalJsonWithoutKey(t *testing.T) {
payload := `{"A": "1", "B": "2"}`
var res struct {
A string `json:""`
B string `json:","`
}
reader := strings.NewReader(payload)
err := UnmarshalJsonReader(reader, &res)
assert.Nil(t, err)
assert.Equal(t, "1", res.A)
assert.Equal(t, "2", res.B)
}

func BenchmarkDefaultValue(b *testing.B) {
for i := 0; i < b.N; i++ {
var a struct {
Ints []int `json:"ints,default=[1,2,3]"`
Strs []string `json:"strs,default=[foo,bar,baz]"`
}
_ = UnmarshalJsonMap(nil, &a)
if len(a.Strs) != 3 || len(a.Ints) != 3 {
b.Fatal("failed")
}
}
}
Loading

0 comments on commit 23deaf5

Please sign in to comment.