From fa647530eaec2b7e06105a3d4a87fe0d101bdb65 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Wed, 7 Nov 2018 15:51:54 +0100 Subject: [PATCH] add cnf/json set() + tests pass --- internal/cnf/json.go | 88 ++++++++++++++++++++++++++++++--- internal/cnf/json_test.go | 101 +++++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 10 deletions(-) diff --git a/internal/cnf/json.go b/internal/cnf/json.go index c65ba86..e9a3ae8 100644 --- a/internal/cnf/json.go +++ b/internal/cnf/json.go @@ -2,6 +2,7 @@ package cnf import ( "encoding/json" + "fmt" "io" "strings" ) @@ -26,29 +27,100 @@ func (d *Json) Parse(_reader io.Reader) error { } -// Get returns the value of a dot-separated path, and if it exists -func (d *Json) Get(_path string) (string, bool) { +// browse returns the target of a dot-separated path (as an interface{} chain where the last is the target if found) +func (d *Json) browse(_path string) ([]interface{}, bool) { // 1. extract path path := strings.Split(_path, ".") - // 2. Iterate over path / nested fields + // 2. init output chain current := d.data + chain := make([]interface{}, 0, len(path)+1) + chain = append(chain, current) + + // 3. iterate over path / nested fields for _, field := range path { fmap, ok := current.(map[string]interface{}) if !ok { // incomplete path - return "", false + return chain, false } child, ok := fmap[field] if !ok { // incomplete path - return "", false + return chain, false } current = child + chain = append(chain, current) } - // 3. Only return if string value - value, ok := current.(string) - return value, ok + return chain, true +} + +// Get returns the value of a dot-separated path, and if it exists +func (d *Json) Get(_path string) (string, bool) { + + // 1. browse path + chain, found := d.browse(_path) + if !found { + return "", false + } + + // 2. return if string value + value, ok := chain[len(chain)-1].(string) + return value, ok + +} + +// Set the value of a dot-separated path, and creates it if not found +func (d *Json) Set(_path, _value string) bool { + + // 1. browse path + create it if does not exist + path := strings.Split(_path, ".") + chain, found := d.browse(_path) + + // 2. if found -> set value + if found { + mapWrapper, ok := chain[len(chain)-2].(map[string]interface{}) + if !ok { // impossible + return false + } + key := path[len(path)-1] + mapWrapper[key] = _value + return true + } + + // 3. create path until the end to set value + root := make(map[string]interface{}) + current := root + + // create children until second to last + fmt.Printf("%d / %d\n", len(chain)-1, len(path)) + for i, l := len(chain)-1, len(path)-1; i < l; i++ { + child := make(map[string]interface{}) + fmt.Printf("add '%s'\n", path[i]) + current[path[i]] = child + current = child + } + + // set value + current[path[len(path)-1]] = _value + + // replace whole object if empty + if len(chain) < 2 { + d.data = root + return true + } + + // update last found object to add the value + wrapper, ok := chain[len(chain)-2].(map[string]interface{}) + if !ok { // impossible + return false + } + + key := path[len(chain)-2] + wrapper[key] = root + + return true + } diff --git a/internal/cnf/json_test.go b/internal/cnf/json_test.go index 5ce828c..2347f9f 100644 --- a/internal/cnf/json_test.go +++ b/internal/cnf/json_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestSimpleJson(t *testing.T) { +func TestGet(t *testing.T) { tests := []struct { raw string @@ -45,7 +45,7 @@ func TestSimpleJson(t *testing.T) { } -func TestNotString(t *testing.T) { +func TestGetNotString(t *testing.T) { tests := []struct { raw string @@ -79,3 +79,100 @@ func TestNotString(t *testing.T) { } } + +func TestSetPathExistsAndIsString(t *testing.T) { + + tests := []struct { + raw string + key string + value string + }{ + {`{ "key": "value" }`, "key", "newvalue"}, + {`{ "ignore": "xxx", "key": "value" }`, "key", "newvalue"}, + {`{ "parent": { "child": "value" } }`, "parent.child", "newvalue"}, + {`{ "ignore": "xxx", "parent": { "child": "value" } }`, "parent.child", "newvalue"}, + } + + for _, test := range tests { + + parser := new(Json) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + err := parser.Parse(reader) + if err != nil { + t.Errorf("parse error: %s", err) + continue + } + + // update value + if !parser.Set(test.key, test.value) { + t.Errorf("cannot set '%s' to '%s'", test.key, test.value) + continue + } + + // check new value + value, found := parser.Get(test.key) + if !found { + t.Errorf("expected a result, got none") + continue + } + + // check value + if value != test.value { + t.Errorf("expected '%s' got '%s'", test.value, value) + } + + } + +} + +func TestSetCreatePath(t *testing.T) { + + tests := []struct { + raw string + key string + value string + }{ + {`{ }`, "key", "newvalue"}, + {`{ }`, "parent.child", "newvalue"}, + {`{ }`, "parent.child.subchild", "newvalue"}, + + {`{ "ignore": "xxx" }`, "key", "newvalue"}, + {`{ "parent": { } }`, "parent.child", "newvalue"}, + {`{ "ignore": "xxx", "parent": { } }`, "parent.child", "newvalue"}, + } + + for i, test := range tests { + + parser := new(Json) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + err := parser.Parse(reader) + if err != nil { + t.Errorf("[%d] parse error: %s", i, err) + continue + } + + // update value + if !parser.Set(test.key, test.value) { + t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value) + continue + } + + // check new value + value, found := parser.Get(test.key) + if !found { + t.Errorf("[%d] expected a result, got none", i) + continue + } + + // check value + if value != test.value { + t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value) + } + + } + +}