Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ templating.
| date | string, time.Time | Returns the text representation of the time in the specified format. For documentation on formats refer to [pkg.go.dev/time](https://pkg.go.dev/time#pkg-constants). |
| dict | values ...any | Returns a map of string to any, constructed from the variadic list of key-value pairs. The number of arguments must be even, and the keys must be strings. |
| humanizeDuration | number or string | Returns a human-readable string representing the duration, and the error if it happened. |
| join | sep string, s []string | [strings.Join](http://golang.org/pkg/strings/#Join), concatenates the elements of s to create a single string. The separator string sep is placed between elements in the resulting string. (note: argument order inverted for easier pipelining in templates.) |
| join | sep string, any | [strings.Join](http://golang.org/pkg/strings/#Join), concatenates the elements of the provided slice to create a single string. The separator string sep is placed between elements in the resulting string. (note: argument order inverted for easier pipelining in templates.) |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document coercion behavior explicitly for join

Line 92 updates the argument type to any, but the description should explicitly say non-string elements are converted to strings before joining (and when errors occur, if any). That avoids ambiguous API expectations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/notifications.md` at line 92, Update the docs for the join filter to
explicitly state coercion behavior: clarify that join (described as "join")
accepts elements of type any and will convert non-string elements to their
string representation (e.g. via standard stringification like fmt.Sprint) before
concatenation, and that it does not return an error on conversion (unless the
implementation documents otherwise); reference the existing strings.Join mention
for string-only behavior and note the argument order inversion for templates.

| list | ...any | Returns the passed arguments as a slice of interfaces. |
| match | pattern, string | [Regexp.MatchString](https://golang.org/pkg/regexp/#MatchString). Match a string using Regexp. |
| now | | [time.Now](https://pkg.go.dev/time#Now), returns the current local time. |
Expand Down
21 changes: 19 additions & 2 deletions template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,25 @@ var DefaultFuncs = FuncMap{
"trimSpace": strings.TrimSpace,
// join is equal to strings.Join but inverts the argument order
// for easier pipelining in templates.
"join": func(sep string, s []string) string {
return strings.Join(s, sep)
"join": func(sep string, v any) (string, error) {
if s, ok := v.([]string); ok {
return strings.Join(s, sep), nil
}

value := reflect.ValueOf(v)
switch {
case !value.IsValid():
return "", nil
case value.Kind() == reflect.Slice, value.Kind() == reflect.Array:
parts := make([]string, 0, value.Len())
for i := 0; i < value.Len(); i++ {
elem := value.Index(i).Interface()
parts = append(parts, fmt.Sprint(elem))
}
return strings.Join(parts, sep), nil
default:
return "", fmt.Errorf("join expects a slice or array, got %T", v)
}
},
"match": regexp.MatchString,
"safeHtml": func(text string) tmplhtml.HTML {
Expand Down
14 changes: 14 additions & 0 deletions template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,16 @@ func TestTemplateExpansion(t *testing.T) {
data: []string{"a", "b", "c"},
exp: "a,b,c",
},
{
title: "Template using join with list",
in: `{{ list "a" "b" "c" | join "," }}`,
exp: "a,b,c",
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{
title: "Template using join with mixed types",
in: `{{ list 1 true "x" | join "," }}`,
exp: "1,true,x",
},
{
title: "Text template without HTML escaping",
in: `{{ "<b>" }}`,
Expand Down Expand Up @@ -621,6 +631,10 @@ func TestTemplateFuncs(t *testing.T) {
in: `{{ . | join "," }}`,
data: []string{"abc", "def"},
exp: "abc,def",
}, {
title: "Template using join with list",
in: `{{ list "abc" "def" | join "," }}`,
exp: "abc,def",
}, {
title: "Template using match",
in: `{{ match "[a-z]+" "abc" }}`,
Expand Down