diff --git a/config.go b/config.go index 4909d8c..8ce2da3 100644 --- a/config.go +++ b/config.go @@ -20,7 +20,7 @@ const ( NullType SubstitutionType ConcatenationType - stringWithAlternativeType + valueWithAlternativeType ) // Config stores the root of the configuration tree @@ -287,19 +287,19 @@ func (s String) Type() Type { return StringType } func (s String) String() string { return strings.Trim(string(s), `"`) } func (s String) isConcatenable() bool { return true } -// stringWithAlternative represents a string value with Substitution which might override the original string value -type stringWithAlternative struct { - value String +// valueWithAlternative represents a value with Substitution which might override the original value +type valueWithAlternative struct { + value Value alternative *Substitution } -func (s *stringWithAlternative) Type() Type { return stringWithAlternativeType } +func (s *valueWithAlternative) Type() Type { return valueWithAlternativeType } -func (s *stringWithAlternative) String() string { +func (s *valueWithAlternative) String() string { return fmt.Sprintf("(%s | %s)", s.value, s.alternative.String()) } -func (s *stringWithAlternative) isConcatenable() bool { return false } +func (s *valueWithAlternative) isConcatenable() bool { return false } // Object represents an object node in the configuration tree type Object map[string]Value diff --git a/config_test.go b/config_test.go index a7182bc..980a1ec 100644 --- a/config_test.go +++ b/config_test.go @@ -442,10 +442,10 @@ func TestSubstitution_String(t *testing.T) { }) } -func TestStringWithAlternative_String(t *testing.T) { - t.Run("return the string of string with alternative", func(t *testing.T) { +func TestValueWithAlternative_String(t *testing.T) { + t.Run("return the string of valueWithAlternative", func(t *testing.T) { substitution := Substitution{path: "a", optional: false} - withAlt := stringWithAlternative{value: "value", alternative: &substitution} + withAlt := valueWithAlternative{value: String("value"), alternative: &substitution} got := withAlt.String() assertEquals(t, got, "(value | ${a})") }) diff --git a/parser.go b/parser.go index 763246e..f2da1be 100644 --- a/parser.go +++ b/parser.go @@ -169,8 +169,8 @@ func processSubstitution(root Object, value Value, resolveFunc func(value Value) } resolveFunc(processed) return nil - } else if valueType == stringWithAlternativeType { - withAlternative := value.(*stringWithAlternative) + } else if valueType == valueWithAlternativeType { + withAlternative := value.(*valueWithAlternative) if withAlternative.alternative != nil { processed, err := processSubstitutionType(root, withAlternative.alternative) if err != nil { @@ -306,16 +306,10 @@ func (p *parser) extractObject(isSubObject ...bool) (Object, error) { (existingValue.Type() == ObjectType && value.Type() == SubstitutionType) || (existingValue.Type() == SubstitutionType && value.Type() == ObjectType) { value = concatenation{existingValue, value} - } else if existingValue.Type() == StringType && value.Type() == SubstitutionType { - value = &stringWithAlternative{ - value: existingValue.(String), - alternative: value.(*Substitution), - } - } else if existingValue.Type() == stringWithAlternativeType && value.Type() == SubstitutionType { - value = &stringWithAlternative{ - value: existingValue.(*stringWithAlternative).value, - alternative: value.(*Substitution), - } + } else if existingValue.Type() == valueWithAlternativeType && value.Type() == SubstitutionType { + value = &valueWithAlternative{value: existingValue, alternative: value.(*Substitution)} + } else if value.Type() == SubstitutionType { + value = &valueWithAlternative{value: existingValue, alternative: value.(*Substitution)} } } diff --git a/parser_test.go b/parser_test.go index 3b39f9a..ce6b6b7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "os" - "reflect" "strings" "testing" "time" @@ -365,6 +364,20 @@ func TestExtractObject(t *testing.T) { assertDeepEqual(t, got, expected) }) + t.Run("return valueWithAlternative object if the current value (after colon separator) is substitution and the existing value is neither a substitution nor object", func(t *testing.T) { + parser := newParser(strings.NewReader("{a:1,a:${?b}}")) + parser.advance() + expected := Object{ + "a": &valueWithAlternative{ + value: Int(1), + alternative: &Substitution{path: "b", optional: true}, + }, + } + got, err := parser.extractObject() + assertNoError(t, err) + assertDeepEqual(t, got, expected) + }) + t.Run("override the existing value if the current value (after colon separator) is object and there is an existing non-object with the same key", func(t *testing.T) { parser := newParser(strings.NewReader("{a:1,a:{c:2}}")) parser.advance() @@ -564,7 +577,7 @@ func TestResolveSubstitutions(t *testing.T) { testEnv := "TEST_ENV" testEnvValue := "test" envSubstitution := &Substitution{path: testEnv, optional: false} - staticWithEnv := &stringWithAlternative{value: "static", alternative: envSubstitution} + staticWithEnv := &valueWithAlternative{value: String("static"), alternative: envSubstitution} object := Object{"a": staticWithEnv} err := os.Setenv(testEnv, testEnvValue) assertNoError(t, err) @@ -582,7 +595,7 @@ func TestResolveSubstitutions(t *testing.T) { t.Run("resolve to the static value if substitution path does not exist and environment variable is not set and default value was not provided", func(t *testing.T) { defaultValue := String("default") envSubstitution := &Substitution{path: "TEST_ENV", optional: true} - staticWithEnv := &stringWithAlternative{value: defaultValue, alternative: envSubstitution} + staticWithEnv := &valueWithAlternative{value: defaultValue, alternative: envSubstitution} object := Object{"a": staticWithEnv} err := resolveSubstitutions(object) assertNoError(t, err) @@ -595,7 +608,7 @@ func TestResolveSubstitutions(t *testing.T) { t.Run("return an error if cannot find required substitution and default value was provided", func(t *testing.T) { defaultValue := String("default") envSubstitution := &Substitution{path: "TEST_ENV", optional: false} - staticWithEnv := &stringWithAlternative{value: defaultValue, alternative: envSubstitution} + staticWithEnv := &valueWithAlternative{value: defaultValue, alternative: envSubstitution} object := Object{"a": staticWithEnv} err := resolveSubstitutions(object) @@ -696,36 +709,58 @@ func TestResolveSubstitutions(t *testing.T) { assertError(t, err, expectedError) }) - t.Run("extract string with alternative value", func(t *testing.T) { - parser := newParser(strings.NewReader("a: static, a:${?b}")) - expected := Object{"a": &stringWithAlternative{ - value: "static", - alternative: &Substitution{ - path: "b", - optional: true, - }, + t.Run("extract valueWithAlternative value with string type", func(t *testing.T) { + parser := newParser(strings.NewReader("a: stringValue, a:${?b}")) + expected := Object{"a": &valueWithAlternative{ + value: String("stringValue"), + alternative: &Substitution{path: "b", optional: true}, }} got, err := parser.extractObject() assertNoError(t, err) - if !reflect.DeepEqual(expected, got) { - t.Errorf("expected: %v, got: %v", expected, got) - } + assertDeepEqual(t, got, expected) }) - t.Run("extract string with alternative value and overwrite alternatives", func(t *testing.T) { - parser := newParser(strings.NewReader("a: static, a:${?c}, a:${?b}")) - expected := Object{"a": &stringWithAlternative{ - value: "static", - alternative: &Substitution{ - path: "b", - optional: true, - }, + t.Run("extract valueWithAlternative value with number type", func(t *testing.T) { + parser := newParser(strings.NewReader("a: 1, a:${?b}")) + expected := Object{"a": &valueWithAlternative{ + value: Int(1), + alternative: &Substitution{path: "b", optional: true}, + }} + got, err := parser.extractObject() + assertNoError(t, err) + assertDeepEqual(t, got, expected) + }) + + t.Run("extract valueWithAlternative value with duration type", func(t *testing.T) { + parser := newParser(strings.NewReader("a: 1s, a:${?b}")) + expected := Object{"a": &valueWithAlternative{ + value: Duration(time.Second), + alternative: &Substitution{path: "b", optional: true}, + }} + got, err := parser.extractObject() + assertNoError(t, err) + assertDeepEqual(t, got, expected) + }) + + t.Run("extract valueWithAlternative value with boolean type", func(t *testing.T) { + parser := newParser(strings.NewReader("a: true, a:${?b}")) + expected := Object{"a": &valueWithAlternative{ + value: Boolean(true), + alternative: &Substitution{path: "b", optional: true}, }} got, err := parser.extractObject() assertNoError(t, err) - if !reflect.DeepEqual(expected, got) { - t.Errorf("expected: %v, got: %v", expected, got) + assertDeepEqual(t, got, expected) + }) + + t.Run("extract valueWithAlternative value and overwrite alternatives", func(t *testing.T) { + parser := newParser(strings.NewReader("a: static, a:${?b}")) + expected := Object{ + "a": &valueWithAlternative{value: String("static"), alternative: &Substitution{path: "b", optional: true}}, } + got, err := parser.extractObject() + assertNoError(t, err) + assertDeepEqual(t, got, expected) }) }