diff --git a/section.go b/section.go index cd79049..f5a69aa 100644 --- a/section.go +++ b/section.go @@ -309,6 +309,44 @@ func (section *Section) Resolve(name string) (Value, error) { return current, nil } +// Merge merges the given section to current section. Settings from source +// section overwites the values in the current section +func (section *Section) Merge(source *Section) error { + for _, key := range source.Keys() { + sourceValue, _ := source.Get(key) + targetValue, err := section.Get(key) + + // not found, so add it + if err != nil { + section.Set(key, sourceValue) + continue + } + + // found existing one and it's type SECTION, merge it + if targetValue.GetType() == SECTION { + // Source value have to be SECTION type here + if sourceValue.GetType() != SECTION { + return fmt.Errorf("source (%v) and target (%v) type doesn't match: %v", + sourceValue.GetType(), + targetValue.GetType(), + key) + } + + if err = targetValue.(*Section).Merge(sourceValue.(*Section)); err != nil { + return err + } + + continue + } + + // found existing one, update it + if err = targetValue.UpdateValue(sourceValue.GetValue()); err != nil { + return fmt.Errorf("%v: %v", err, key) + } + } + return nil +} + // ToJSON will convert this Section and all it's underlying values and Sections // into JSON as a []byte func (section *Section) ToJSON() ([]byte, error) { diff --git a/section_test.go b/section_test.go index 26529aa..d7104b0 100644 --- a/section_test.go +++ b/section_test.go @@ -1,6 +1,8 @@ package forge_test import ( + "encoding/json" + "fmt" "testing" "github.com/brettlangdon/forge" @@ -30,3 +32,122 @@ func TestSectionKeys(t *testing.T) { t.Error("expected 'key3' to be in the list of keys") } } + +func TestMergeSection(t *testing.T) { + config1Str := ` + global = "global value"; + + prod { + value = "string value"; + integer = 500 + float = 80.80 + boolean = true + negative = FALSE + nothing = NULL + } + ` + + config2Str := ` + integer = 500 + float = 80.80 + boolean = true + negative = FALSE + nothing = NULL + + new_section { + integer = 500 + float = 80.80 + boolean = true + negative = FALSE + nothing = NULL + } + + prod { + value = "new value"; + secret = "shhh"; + nothing = "value" + negative = false + boolean = false + } + ` + + config1, err := forge.ParseString(config1Str) + if err != nil { + t.Error(err) + t.FailNow() + } + + config2, err := forge.ParseString(config2Str) + if err != nil { + t.Error(err) + t.FailNow() + } + + err = config1.Merge(config2) + if err != nil { + t.Error(err) + t.FailNow() + } + + // Validation + valueFloat, _ := config1.GetFloat("float") + if valueFloat != float64(80.80) { + t.Errorf("Excepted '80.80' got %v", valueFloat) + } + + valueNegative, _ := config1.Resolve("new_section.negative") + if valueNegative.GetValue().(bool) { + t.Errorf("Excepted 'false' got %v", valueNegative) + } + + valueString, _ := config1.Resolve("prod.value") + if valueString.GetValue().(string) == "string value" { + t.Errorf("Excepted 'new value' got %v", valueString) + } + + valueBoolean, _ := config1.Resolve("prod.boolean") + if valueBoolean.GetValue().(bool) { + t.Errorf("Excepted 'false' got %v", valueBoolean) + } + + bytes, _ := json.MarshalIndent(config1.ToMap(), "", " ") + fmt.Println(string(bytes)) +} + +func TestMergeSectionFailSectionToField(t *testing.T) { + config1Str := ` + global = "global value"; + + prod { + value = "string value"; + integer = 500 + float = 80.80 + boolean = true + negative = FALSE + nothing = NULL + } + ` + + config2Str := ` + global = "global value"; + + prod = "I'm prod value" + ` + + config1, err := forge.ParseString(config1Str) + if err != nil { + t.Error(err) + t.FailNow() + } + + config2, err := forge.ParseString(config2Str) + if err != nil { + t.Error(err) + t.FailNow() + } + + err = config1.Merge(config2) + if err.Error() != "source (STRING) and target (SECTION) type doesn't match: prod" { + t.Error(err) + } +}