forked from elastic/beats
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This method implements a way to put dotted keys into a MapStr while ensuring they don't override each other. For example: ``` a := MapStr{} a.Put("com.docker.swarm.task", "x") a.Put("com.docker.swarm.task.id", 1) a.Put("com.docker.swarm.task.name", "foobar") ``` Will result in `{"com":{"docker":{"swarm":{"task":"x"}}}}` DeDotPut detects this scenario and renames the common base key, by appending `.value`: ``` a := MapStr{} safemapstr.Put(a, "com.docker.swarm.task", "x") safemapstr.Put(a, "com.docker.swarm.task.id", 1) safemapstr.Put(a, "com.docker.swarm.task.name", "foobar") ``` Will result in `{"com":{"docker":{"swarm":{"task":{"id":1,"name":"foobar","value":"x"}}}}}` The plan is to use this method to convert all unstructured external data, like labels, annotations and so Related to elastic#6421
- Loading branch information
Carlos Pérez-Aradros Herce
committed
Feb 25, 2018
1 parent
4b83474
commit 665d935
Showing
2 changed files
with
131 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package safemapstr | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
) | ||
|
||
// Put This method implements a way to put dotted keys into a MapStr while | ||
// ensuring they don't override each other. For example: | ||
// | ||
// a := MapStr{} | ||
// safemapstr.Put(a, "com.docker.swarm.task", "x") | ||
// safemapstr.Put(a, "com.docker.swarm.task.id", 1) | ||
// safemapstr.Put(a, "com.docker.swarm.task.name", "foobar") | ||
// | ||
// Will result in `{"com":{"docker":{"swarm":{"task":{"id":1,"name":"foobar","value":"x"}}}}}` | ||
// | ||
// Put detects this scenario and renames the common base key, by appending | ||
// `.value` | ||
func Put(data common.MapStr, key string, value interface{}) error { | ||
keyParts := strings.SplitN(key, ".", 2) | ||
|
||
// If leaf node or key exists directly | ||
if len(keyParts) == 1 { | ||
oldValue, exists := data[key] | ||
if exists { | ||
switch oldValue.(type) { | ||
case common.MapStr: | ||
// This would replace a whole object, change its key to avoid that: | ||
oldValue.(common.MapStr)["value"] = value | ||
return nil | ||
} | ||
} | ||
data[key] = value | ||
return nil | ||
} | ||
|
||
// Checks if first part of the key exists | ||
k := keyParts[0] | ||
d, exists := data[k] | ||
if !exists { | ||
d = common.MapStr{} | ||
data[k] = d | ||
} | ||
|
||
v, ok := tryToMapStr(d) | ||
if !ok { | ||
// This would replace a leaf with an object, change its key to avoid that: | ||
v = common.MapStr{"value": d} | ||
data[k] = v | ||
} | ||
|
||
return Put(v, keyParts[1], value) | ||
} | ||
|
||
func tryToMapStr(v interface{}) (common.MapStr, bool) { | ||
switch m := v.(type) { | ||
case common.MapStr: | ||
return m, true | ||
case map[string]interface{}: | ||
return common.MapStr(m), true | ||
default: | ||
return nil, false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package safemapstr | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestPut(t *testing.T) { | ||
m := common.MapStr{ | ||
"subMap": common.MapStr{ | ||
"a": 1, | ||
}, | ||
} | ||
|
||
// Add new value to the top-level. | ||
err := Put(m, "a", "ok") | ||
assert.NoError(t, err) | ||
assert.Equal(t, common.MapStr{"a": "ok", "subMap": common.MapStr{"a": 1}}, m) | ||
|
||
// Add new value to subMap. | ||
err = Put(m, "subMap.b", 2) | ||
assert.NoError(t, err) | ||
assert.Equal(t, common.MapStr{"a": "ok", "subMap": common.MapStr{"a": 1, "b": 2}}, m) | ||
|
||
// Overwrite a value in subMap. | ||
err = Put(m, "subMap.a", 2) | ||
assert.NoError(t, err) | ||
assert.Equal(t, common.MapStr{"a": "ok", "subMap": common.MapStr{"a": 2, "b": 2}}, m) | ||
|
||
// Add value to map that does not exist. | ||
m = common.MapStr{} | ||
err = Put(m, "subMap.newMap.a", 1) | ||
assert.NoError(t, err) | ||
assert.Equal(t, common.MapStr{"subMap": common.MapStr{"newMap": common.MapStr{"a": 1}}}, m) | ||
} | ||
|
||
func TestPutRenames(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
a := common.MapStr{} | ||
Put(a, "com.docker.swarm.task", "x") | ||
Put(a, "com.docker.swarm.task.id", 1) | ||
Put(a, "com.docker.swarm.task.name", "foobar") | ||
assert.Equal(common.MapStr{"com": common.MapStr{"docker": common.MapStr{"swarm": common.MapStr{ | ||
"task": common.MapStr{ | ||
"id": 1, | ||
"name": "foobar", | ||
"value": "x", | ||
}}}}}, a) | ||
|
||
// order is not important: | ||
b := common.MapStr{} | ||
Put(b, "com.docker.swarm.task.id", 1) | ||
Put(b, "com.docker.swarm.task.name", "foobar") | ||
Put(b, "com.docker.swarm.task", "x") | ||
assert.Equal(common.MapStr{"com": common.MapStr{"docker": common.MapStr{"swarm": common.MapStr{ | ||
"task": common.MapStr{ | ||
"id": 1, | ||
"name": "foobar", | ||
"value": "x", | ||
}}}}}, b) | ||
} |