Skip to content

Commit e33fbab

Browse files
feat: enable original key to be returned for metadata property fields (dapr#88)
Signed-off-by: Samantha Coyle <[email protected]> Signed-off-by: ItalyPaleAle <[email protected]> Co-authored-by: ItalyPaleAle <[email protected]>
1 parent 050e34c commit e33fbab

File tree

3 files changed

+92
-3
lines changed

3 files changed

+92
-3
lines changed

metadata/properties.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2024 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package metadata
15+
16+
// Properties contains metadata properties, as a key-value dictionary
17+
type Properties map[string]string
18+
19+
// GetProperty returns a property from the metadata, with support for case-insensitive keys and aliases.
20+
func (p Properties) GetProperty(keys ...string) (val string, ok bool) {
21+
return GetMetadataProperty(p, keys...)
22+
}
23+
24+
// GetPropertyWithMatchedKey returns a property from the metadata, with support for case-insensitive keys and aliases,
25+
// while returning the original matching metadata field key.
26+
func (p Properties) GetPropertyWithMatchedKey(keys ...string) (key string, val string, ok bool) {
27+
return GetMetadataPropertyWithMatchedKey(p, keys...)
28+
}
29+
30+
// Decode decodes metadata into a struct.
31+
// This is an extension of mitchellh/mapstructure which also supports decoding durations.
32+
func (p Properties) Decode(result any) error {
33+
return decodeMetadataMap(p, result)
34+
}

metadata/utils.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,24 @@ import (
2424

2525
// GetMetadataProperty returns a property from the metadata map, with support for case-insensitive keys and aliases.
2626
func GetMetadataProperty(props map[string]string, keys ...string) (val string, ok bool) {
27+
_, val, ok = GetMetadataPropertyWithMatchedKey(props, keys...)
28+
return val, ok
29+
}
30+
31+
// GetMetadataPropertyWithMatchedKey returns a property from the metadata map, with support for case-insensitive keys and aliases,
32+
// while returning the original matching metadata field key.
33+
func GetMetadataPropertyWithMatchedKey(props map[string]string, keys ...string) (key string, val string, ok bool) {
2734
lcProps := make(map[string]string, len(props))
2835
for k, v := range props {
2936
lcProps[strings.ToLower(k)] = v
3037
}
3138
for _, k := range keys {
3239
val, ok = lcProps[strings.ToLower(k)]
3340
if ok {
34-
return val, true
41+
return k, val, true
3542
}
3643
}
37-
return "", false
44+
return "", "", false
3845
}
3946

4047
// DecodeMetadata decodes a component metadata into a struct.
@@ -55,8 +62,12 @@ func DecodeMetadata(input any, result any) error {
5562
return fmt.Errorf("input object cannot be cast to map[string]string: %w", err)
5663
}
5764

65+
return decodeMetadataMap(inputMap, result)
66+
}
67+
68+
func decodeMetadataMap(inputMap map[string]string, result any) error {
5869
// Handle aliases
59-
err = resolveAliases(inputMap, reflect.TypeOf(result))
70+
err := resolveAliases(inputMap, reflect.TypeOf(result))
6071
if err != nil {
6172
return fmt.Errorf("failed to resolve aliases: %w", err)
6273
}

metadata/utils_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,47 @@ func TestResolveAliases(t *testing.T) {
395395
})
396396
}
397397
}
398+
399+
func TestGetMetadataPropertyWithMatchedKey(t *testing.T) {
400+
props := map[string]string{
401+
"key1": "value1",
402+
"key2": "value2",
403+
"key3": "value3",
404+
"emptyKey": "",
405+
}
406+
407+
t.Run("Existing key", func(t *testing.T) {
408+
key, val, ok := GetMetadataPropertyWithMatchedKey(props, "key1", "key2")
409+
assert.True(t, ok)
410+
assert.Equal(t, "key1", key)
411+
assert.Equal(t, "value1", val)
412+
})
413+
414+
t.Run("Case-insensitive matching", func(t *testing.T) {
415+
key, val, ok := GetMetadataPropertyWithMatchedKey(props, "KEY1")
416+
assert.True(t, ok)
417+
assert.Equal(t, "KEY1", key)
418+
assert.Equal(t, "value1", val)
419+
})
420+
421+
t.Run("Non-existing key", func(t *testing.T) {
422+
key, val, ok := GetMetadataPropertyWithMatchedKey(props, "key4")
423+
assert.False(t, ok)
424+
assert.Equal(t, "", key)
425+
assert.Equal(t, "", val)
426+
})
427+
428+
t.Run("Empty properties", func(t *testing.T) {
429+
key, val, ok := GetMetadataPropertyWithMatchedKey(nil, "key1")
430+
assert.False(t, ok)
431+
assert.Equal(t, "", key)
432+
assert.Equal(t, "", val)
433+
})
434+
435+
t.Run("Value is empty", func(t *testing.T) {
436+
key, val, ok := GetMetadataPropertyWithMatchedKey(props, "EmptyKey")
437+
assert.True(t, ok)
438+
assert.Equal(t, "EmptyKey", key)
439+
assert.Equal(t, "", val)
440+
})
441+
}

0 commit comments

Comments
 (0)