diff --git a/bundle/config/interpolation/interpolation.go b/bundle/config/interpolation/interpolation.go index f253241d..09dfc79a 100644 --- a/bundle/config/interpolation/interpolation.go +++ b/bundle/config/interpolation/interpolation.go @@ -2,6 +2,7 @@ package interpolation import ( "context" + "errors" "fmt" "reflect" "regexp" @@ -39,16 +40,23 @@ func (s *stringField) dependsOn() []string { return out } -func (s *stringField) interpolate(fn LookupFunction, lookup map[string]string) { +func (s *stringField) interpolate(fns []LookupFunction, lookup map[string]string) { out := re.ReplaceAllStringFunc(s.Get(), func(s string) string { // Turn the whole match into the submatch. match := re.FindStringSubmatch(s) - v, err := fn(match[1], lookup) - if err != nil { - panic(err) + for _, fn := range fns { + v, err := fn(match[1], lookup) + if errors.Is(err, ErrSkipInterpolation) { + continue + } + if err != nil { + panic(err) + } + return v } - return v + // No substitution. + return s }) s.Set(out) @@ -163,7 +171,7 @@ func (a *accumulator) start(v any) { a.walk([]string{}, rv, nilSetter{}) } -func (a *accumulator) expand(fn LookupFunction) error { +func (a *accumulator) expand(fns ...LookupFunction) error { for path, v := range a.strings { ds := v.dependsOn() if len(ds) == 0 { @@ -176,24 +184,24 @@ func (a *accumulator) expand(fn LookupFunction) error { return fmt.Errorf("cannot interpolate %s: %w", path, err) } - v.interpolate(fn, m) + v.interpolate(fns, m) } return nil } type interpolate struct { - fn LookupFunction + fns []LookupFunction } func (m *interpolate) expand(v any) error { a := accumulator{} a.start(v) - return a.expand(m.fn) + return a.expand(m.fns...) } -func Interpolate(fn LookupFunction) bundle.Mutator { - return &interpolate{fn: fn} +func Interpolate(fns ...LookupFunction) bundle.Mutator { + return &interpolate{fns: fns} } func (m *interpolate) Name() string { diff --git a/bundle/config/interpolation/lookup.go b/bundle/config/interpolation/lookup.go index 93535cdd..932d739e 100644 --- a/bundle/config/interpolation/lookup.go +++ b/bundle/config/interpolation/lookup.go @@ -1,6 +1,7 @@ package interpolation import ( + "errors" "fmt" "strings" @@ -10,6 +11,9 @@ import ( // LookupFunction returns the value to rewrite a path expression to. type LookupFunction func(path string, depends map[string]string) (string, error) +// ErrSkipInterpolation can be used to fall through from [LookupFunction]. +var ErrSkipInterpolation = errors.New("skip interpolation") + // DefaultLookup looks up the specified path in the map. // It returns an error if it doesn't exist. func DefaultLookup(path string, lookup map[string]string) (string, error) { @@ -29,7 +33,7 @@ func pathPrefixMatches(prefix []string, path string) bool { func ExcludeLookupsInPath(exclude ...string) LookupFunction { return func(path string, lookup map[string]string) (string, error) { if pathPrefixMatches(exclude, path) { - return fmt.Sprintf("${%s}", path), nil + return "", ErrSkipInterpolation } return DefaultLookup(path, lookup) @@ -39,10 +43,10 @@ func ExcludeLookupsInPath(exclude ...string) LookupFunction { // IncludeLookupsInPath is a lookup function that limits lookups to the specified path. func IncludeLookupsInPath(include ...string) LookupFunction { return func(path string, lookup map[string]string) (string, error) { - if pathPrefixMatches(include, path) { - return DefaultLookup(path, lookup) + if !pathPrefixMatches(include, path) { + return "", ErrSkipInterpolation } - return fmt.Sprintf("${%s}", path), nil + return DefaultLookup(path, lookup) } } diff --git a/bundle/config/interpolation/lookup_test.go b/bundle/config/interpolation/lookup_test.go index 594c5af8..61628bf0 100644 --- a/bundle/config/interpolation/lookup_test.go +++ b/bundle/config/interpolation/lookup_test.go @@ -31,7 +31,9 @@ func fixture() interpolationFixture { func TestExcludePath(t *testing.T) { tmp := fixture() m := interpolate{ - fn: ExcludeLookupsInPath("a"), + fns: []LookupFunction{ + ExcludeLookupsInPath("a"), + }, } err := m.expand(&tmp) @@ -46,7 +48,9 @@ func TestExcludePath(t *testing.T) { func TestIncludePath(t *testing.T) { tmp := fixture() m := interpolate{ - fn: IncludeLookupsInPath("a"), + fns: []LookupFunction{ + IncludeLookupsInPath("a"), + }, } err := m.expand(&tmp) @@ -57,3 +61,21 @@ func TestIncludePath(t *testing.T) { assert.Equal(t, "1", tmp.C["ax"]) assert.Equal(t, "${b.x}", tmp.C["bx"]) } + +func TestIncludePathMultiple(t *testing.T) { + tmp := fixture() + m := interpolate{ + fns: []LookupFunction{ + IncludeLookupsInPath("a"), + IncludeLookupsInPath("b"), + }, + } + + err := m.expand(&tmp) + require.NoError(t, err) + + assert.Equal(t, "1", tmp.A["x"]) + assert.Equal(t, "2", tmp.B["x"]) + assert.Equal(t, "1", tmp.C["ax"]) + assert.Equal(t, "2", tmp.C["bx"]) +}