Skip to content

Commit

Permalink
Add safemapstr to provide Put:
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
66 changes: 66 additions & 0 deletions libbeat/common/safemapstr/safemapstr.go
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
}
}
65 changes: 65 additions & 0 deletions libbeat/common/safemapstr/safemapstr_test.go
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)
}

0 comments on commit 665d935

Please sign in to comment.