-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of github.com:Azure/azure-dev into fix-387
- Loading branch information
Showing
4 changed files
with
237 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package apphost | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// evalString evaluates a given string expression, using the provided evalExpr function to produce values for expressions | ||
// in the string. It supports strings that contain expressions of the form "{expression}" where "expression" is any string | ||
// that does not contain a '}' character. The evalExpr function is called with the expression (without the enclosing '{' | ||
// and '}' characters) and should return the value to be substituted into the string. If the evalExpr function returns | ||
// an error, evalString will return that error. The '{' and '}' characters can be escaped by doubling them, e.g. | ||
// "{{" and "}}". If a string is malformed (e.g. an unmatched '{' or '}' character), evalString will return an error. | ||
func evalString(src string, evalExpr func(string) (string, error)) (string, error) { | ||
var res strings.Builder | ||
|
||
for i := 0; i < len(src); i++ { | ||
switch src[i] { | ||
case '{': | ||
if i+1 < len(src) && src[i+1] == '{' { | ||
res.WriteByte('{') | ||
i++ | ||
continue | ||
} | ||
|
||
closed := false | ||
for j := i + 1; j < len(src); j++ { | ||
if src[j] == '}' { | ||
v, err := evalExpr(src[i+1 : j]) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
res.WriteString(v) | ||
i = j | ||
closed = true | ||
break | ||
} | ||
} | ||
|
||
if !closed { | ||
return "", fmt.Errorf("unclosed '{' at position %d", i) | ||
} | ||
case '}': | ||
if i+1 < len(src) && src[i+1] == '}' { | ||
res.WriteByte('}') | ||
i++ | ||
continue | ||
} | ||
return "", fmt.Errorf("unexpected '}' at position %d", i) | ||
default: | ||
res.WriteByte(src[i]) | ||
} | ||
} | ||
|
||
return res.String(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package apphost | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestEvalString(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
src string | ||
want string | ||
}{ | ||
{name: "simple", src: "a string with no replacements", want: "a string with no replacements"}, | ||
{name: "replacement", src: "{this.one.has.a.replacement}", want: "this.one.has.a.replacement"}, | ||
{name: "complex", src: "this {one} has {many} replacements", want: "this one has many replacements"}, | ||
{name: "escape", src: "this {{one}} is {{escaped}}", want: "this {one} is {escaped}"}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.name, func(t *testing.T) { | ||
res, err := evalString(c.src, func(s string) (string, error) { | ||
return s, nil | ||
}) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, c.want, res) | ||
}) | ||
} | ||
|
||
errorCases := []struct { | ||
name string | ||
src string | ||
}{ | ||
{name: "unclosed open", src: "this { is unclosed"}, | ||
{name: "unmatched close", src: "this } is unmatched"}, | ||
{name: "unmatched escaped close", src: "this {}} is unmatched"}, | ||
{name: "unmatched escaped open", src: "this {{} is unmatched"}, | ||
} | ||
|
||
for _, c := range errorCases { | ||
t.Run(c.name, func(t *testing.T) { | ||
res, err := evalString(c.src, func(s string) (string, error) { | ||
return s, nil | ||
}) | ||
|
||
assert.Error(t, err) | ||
assert.Equal(t, "", res) | ||
}) | ||
} | ||
|
||
res, err := evalString("{this.one.has.a.replacement}", func(s string) (string, error) { | ||
return "", fmt.Errorf("this should cause evalString to fail") | ||
}) | ||
|
||
assert.Error(t, err) | ||
assert.Equal(t, "", res) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.