Skip to content

Commit b6e6cdc

Browse files
committed
Fix precedence and add tests for required variables
Signed-off-by: Ulysses Souza <[email protected]>
1 parent a6715bc commit b6e6cdc

File tree

3 files changed

+85
-7
lines changed

3 files changed

+85
-7
lines changed

interpolation/interpolation_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package interpolation
1818

1919
import (
20+
"fmt"
2021
"strconv"
2122
"testing"
2223

@@ -98,16 +99,29 @@ func TestValidUnexistentInterpolation(t *testing.T) {
9899
var testcases = []struct {
99100
test string
100101
expected string
102+
errMsg string
101103
}{
102104
{test: "{{{ ${FOO:-foo_} }}}", expected: "{{{ foo_ }}}"},
103105
{test: "{{{ ${FOO:-foo-bar-value} }}}", expected: "{{{ foo-bar-value }}}"},
106+
{test: "{{{ ${FOO:-foo?bar?value} }}}", expected: "{{{ foo?bar?value }}}"},
107+
{test: "{{{ ${FOO:-foo:?bar:?value} }}}", expected: "{{{ foo:?bar:?value }}}"},
104108
{test: "{{{ ${FOO:-foo} ${BAR:-DEFAULT_VALUE} }}}", expected: "{{{ foo DEFAULT_VALUE }}}"},
105109
{test: "{{{ ${BAR} }}}", expected: "{{{ }}}"},
106110
{test: "${FOO:-baz} }}}", expected: "baz }}}"},
107111
{test: "${FOO-baz} }}}", expected: "baz }}}"},
112+
113+
{test: "{{{ ${FOO:?foo_} }}}", errMsg: "foo_"},
114+
{test: "{{{ ${FOO:?foo-bar-value} }}}", errMsg: "foo-bar-value"},
115+
{test: "{{{ ${FOO:?foo} ${BAR:-DEFAULT_VALUE} }}}", errMsg: "foo"},
116+
{test: "{{{ ${BAR} }}}", expected: "{{{ }}}"},
117+
{test: "${FOO:?baz} }}}", errMsg: "baz"},
118+
{test: "${FOO?baz} }}}", errMsg: "baz"},
108119
}
109120

110121
getServiceConfig := func(val string) map[string]interface{} {
122+
if val == "" {
123+
return map[string]interface{}{}
124+
}
111125
return map[string]interface{}{
112126
"myservice": map[string]interface{}{
113127
"environment": map[string]interface{}{
@@ -117,9 +131,17 @@ func TestValidUnexistentInterpolation(t *testing.T) {
117131
}
118132
}
119133

134+
getFullErrorMsg := func(msg string) string {
135+
return fmt.Sprintf("invalid interpolation format for myservice.environment.TESTVAR: "+
136+
"\"required variable FOO is missing a value: %s\". You may need to escape any $ with another $.", msg)
137+
}
138+
120139
for _, testcase := range testcases {
121140
result, err := Interpolate(getServiceConfig(testcase.test), Options{})
122-
assert.NilError(t, err)
141+
if testcase.errMsg != "" {
142+
assert.Assert(t, err != nil, fmt.Sprintf("This should result in an error %q", testcase.errMsg))
143+
assert.Equal(t, getFullErrorMsg(testcase.errMsg), err.Error())
144+
}
123145
assert.Check(t, is.DeepEqual(getServiceConfig(testcase.expected), result))
124146
}
125147
}

template/template.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,7 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
6161
// It accepts additional substitute function.
6262
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
6363
if len(subsFuncs) == 0 {
64-
subsFuncs = []SubstituteFunc{
65-
softDefault,
66-
hardDefault,
67-
requiredNonEmpty,
68-
required,
69-
}
64+
subsFuncs = getDefaultSortedSubstitutionFunctions(template)
7065
}
7166
var err error
7267
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
@@ -126,6 +121,25 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
126121
return result, err
127122
}
128123

124+
func getDefaultSortedSubstitutionFunctions(template string, fns ...SubstituteFunc) []SubstituteFunc {
125+
hyphenIndex := strings.IndexByte(template, '-')
126+
questionIndex := strings.IndexByte(template, '?')
127+
if hyphenIndex < 0 || hyphenIndex > questionIndex {
128+
return []SubstituteFunc{
129+
requiredNonEmpty,
130+
required,
131+
softDefault,
132+
hardDefault,
133+
}
134+
}
135+
return []SubstituteFunc{
136+
softDefault,
137+
hardDefault,
138+
requiredNonEmpty,
139+
required,
140+
}
141+
}
142+
129143
func getFirstBraceClosingIndex(s string) int {
130144
openVariableBraces := 0
131145
for i := 0; i < len(s); i++ {

template/template_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,48 @@ func TestSubstituteWithCustomFunc(t *testing.T) {
262262
assert.Check(t, is.ErrorContains(err, "required variable"))
263263
}
264264

265+
// TestPrecedence tests is the precedence on '-' and '?' is of the first match
266+
func TestPrecedence(t *testing.T) {
267+
268+
testCases := []struct {
269+
template string
270+
expected string
271+
err error
272+
}{
273+
{
274+
template: "${UNSET_VAR?bar-baz}", // Unexistent variable
275+
expected: "",
276+
err: &InvalidTemplateError{
277+
Template: "required variable UNSET_VAR is missing a value: bar-baz",
278+
},
279+
},
280+
{
281+
template: "${UNSET_VAR-myerror?msg}", // Unexistent variable
282+
expected: "myerror?msg",
283+
err: nil,
284+
},
285+
286+
{
287+
template: "${FOO?bar-baz}", // Existent variable
288+
expected: "first",
289+
},
290+
{
291+
template: "${BAR:-default_value_for_empty_var}", // Existent empty variable
292+
expected: "default_value_for_empty_var",
293+
},
294+
{
295+
template: "${UNSET_VAR-default_value_for_unset_var}", // Unset variable
296+
expected: "default_value_for_unset_var",
297+
},
298+
}
299+
300+
for _, tc := range testCases {
301+
result, err := Substitute(tc.template, defaultMapping)
302+
assert.Check(t, is.DeepEqual(tc.err, err))
303+
assert.Check(t, is.Equal(tc.expected, result))
304+
}
305+
}
306+
265307
func TestExtractVariables(t *testing.T) {
266308
testCases := []struct {
267309
name string

0 commit comments

Comments
 (0)