Skip to content

Commit

Permalink
♻️ update: refactoring the slice item validate logic
Browse files Browse the repository at this point in the history
- add new method Validation.ValidateErr(scene ...string) error
  • Loading branch information
inhere committed Jul 24, 2023
1 parent 7fe7ccd commit e5a13ce
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 30 deletions.
41 changes: 33 additions & 8 deletions maputil/get.go
Expand Up @@ -26,6 +26,21 @@ func QuietGet(mp map[string]any, path string) (val any) {
return
}

// GetFromAny get value by key path from any(map,slice) data. eg "top" "top.sub"
func GetFromAny(path string, data any) (val any, ok bool) {
// empty data
if data == nil {
return nil, false
}

keys := strings.Split(path, ".")
if len(keys) == 0 {
return data, true
}

return getByPathKeys(data, keys)
}

// GetByPath get value by key path from a map(map[string]any). eg "top" "top.sub"
func GetByPath(path string, mp map[string]any) (val any, ok bool) {
if val, ok := mp[path]; ok {
Expand Down Expand Up @@ -60,14 +75,19 @@ func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {

// find top item data use top key
var item any

topK := keys[0]
if item, ok = mp[topK]; !ok {
return
}

// find sub item data use sub key
for i, k := range keys[1:] {
return getByPathKeys(item, keys[1:])
}

func getByPathKeys(item any, keys []string) (val any, ok bool) {
kl := len(keys)

for i, k := range keys {
switch tData := item.(type) {
case map[string]string: // is string map
if item, ok = tData[k]; !ok {
Expand All @@ -83,14 +103,14 @@ func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
}
case []map[string]any: // is an any-map slice
if k == Wildcard {
if kl == i+2 { // * is last key
if kl == i+1 { // * is last key
return tData, true
}

// * is not last key, find sub item data
sl := make([]any, 0)
sl := make([]any, 0, len(tData))
for _, v := range tData {
if val, ok = GetByPathKeys(v, keys[i+2:]); ok {
if val, ok = getByPathKeys(v, keys[i+1:]); ok {
sl = append(sl, val)
}
}
Expand All @@ -108,7 +128,7 @@ func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
}
item = tData[idx]
default:
if k == Wildcard && kl == i+2 { // * is last key
if k == Wildcard && kl == i+1 { // * is last key
return tData, true
}

Expand All @@ -117,15 +137,15 @@ func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
if rv.Kind() == reflect.Slice {
if k == Wildcard {
// * is not last key, find sub item data
sl := make([]any, 0)
sl := make([]any, 0, rv.Len())
for si := 0; si < rv.Len(); si++ {
el := reflects.Indirect(rv.Index(si))
if el.Kind() != reflect.Map {
return nil, false
}

// el is map value.
if val, ok = GetByPathKeys(ToAnyMap(el.Interface()), keys[i+2:]); ok {
if val, ok = getByPathKeys(el.Interface(), keys[i+1:]); ok {
sl = append(sl, val)
}
}
Expand All @@ -149,6 +169,11 @@ func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
// as error
return nil, false
}

// next is last key and it is *
if kl == i+2 && keys[i+1] == Wildcard {
return item, true
}
}

return item, true
Expand Down
80 changes: 58 additions & 22 deletions maputil/get_test.go
Expand Up @@ -69,9 +69,9 @@ func TestGetByPath(t *testing.T) {
assert.Eq(t, tt.want, v, tt.path)
}

// v, ok := maputil.GetByPath("mlMp.*.names.1", mp)
// assert.True(t, ok)
// assert.Eq(t, []any{"abc", "def"}, v)
v, ok := maputil.GetByPath("mlMp.*.names.1", mp)
assert.True(t, ok)
assert.Eq(t, []any{"abc", "def"}, v)
}

var mlMp = map[string]any{
Expand Down Expand Up @@ -104,10 +104,10 @@ var mlMp = map[string]any{
// "resident_provider": "Test Resident Provider",
},
{
"code": "OBS01",
"encounter_uid": "3",
"work_item_uid": "4",
"billing_provider": "Test provider OBS01",
"code": "OBS01",
"encounter_uid": "3",
"work_item_uid": "4",
// "billing_provider": "Test provider OBS01",
"resident_provider": "Test Resident Provider",
},
{
Expand All @@ -124,20 +124,32 @@ var mlMp = map[string]any{
}

func TestGetByPath_deepPath(t *testing.T) {
val, ok := maputil.GetByPath("coding.0.details.em.code", mlMp)
assert.True(t, ok)
assert.NotEmpty(t, val)
t.Run("direct multi level key", func(t *testing.T) {
val, ok := maputil.GetByPath("coding.0.details.em.code", mlMp)
assert.True(t, ok)
assert.NotEmpty(t, val)
})

val, ok = maputil.GetByPath("coding.*.details", mlMp)
assert.True(t, ok)
assert.NotEmpty(t, val)
// dump.P(ok, val)
t.Run("dot star 2-level", func(t *testing.T) {
val, ok := maputil.GetByPath("coding.*.details", mlMp)
assert.True(t, ok)
assert.NotEmpty(t, val)
// dump.P(ok, val)
})

val, ok = maputil.GetByPath("coding.*.details.em", mlMp)
dump.P(ok, val)
assert.True(t, ok)
t.Run("dot star 3-level", func(t *testing.T) {
val, ok := maputil.GetByPath("coding.*.details.em", mlMp)
dump.P(ok, val)
assert.True(t, ok)
})

t.Run("last is dot star", func(t *testing.T) {
val, ok := maputil.GetByPath("coding.*.details.em.*", mlMp)
dump.P(ok, val)
assert.True(t, ok)
})

val, ok = maputil.GetByPath("coding.*.details.em.code", mlMp)
val, ok := maputil.GetByPath("coding.*.details.em.code", mlMp)
dump.P(ok, val)
assert.True(t, ok)
assert.IsType(t, []any{}, val)
Expand All @@ -148,10 +160,12 @@ func TestGetByPath_deepPath(t *testing.T) {
assert.Len(t, val, 1)
assert.IsType(t, []any{}, val)

val, ok = maputil.GetByPath("coding.*.details.cpt.*.work_item_uid", mlMp)
// dump.P(ok, val)
assert.True(t, ok)
assert.IsType(t, []any{}, val)
t.Run("missing a field", func(t *testing.T) {
val, ok = maputil.GetByPath("coding.*.details.cpt.*.billing_provider", mlMp)
dump.P(ok, val)
assert.True(t, ok)
assert.IsType(t, []any{}, val)
})

val, ok = maputil.GetByPath("coding.*.details.cpt.*.resident_provider", mlMp)
// dump.P(ok, val)
Expand All @@ -163,6 +177,28 @@ func TestGetByPath_deepPath(t *testing.T) {
assert.False(t, ok)
}

func TestGetFromAny_sliceSubValue(t *testing.T) {
val, ok := maputil.GetByPath("coding.*.details.cpt", mlMp)
assert.True(t, ok)
assert.IsKind(t, reflect.Slice, val)
dump.P(val)

// get sub value in slice
for _, sl := range val.([]any) {
val, ok = maputil.GetFromAny("*.code", sl)
assert.True(t, ok)
dump.P(val)
}

val, ok = maputil.GetFromAny("", map[string]any{"a": "b"})
assert.True(t, ok)
assert.NotEmpty(t, val)

val, ok = maputil.GetFromAny("a", nil)
assert.False(t, ok)
assert.Nil(t, val)
}

func TestKeys(t *testing.T) {
mp := map[string]any{
"key0": "v0",
Expand Down

0 comments on commit e5a13ce

Please sign in to comment.