Skip to content

Commit 027f11a

Browse files
template: preserve quoted numeric strings in DeepCopyWithTemplate (#5098)
This fixes an issue where a quoted numeric string produced by DeepCopyWithTemplate could end up being serialized as an integer instead of a string. One concrete case is Jira custom fields that expect a payload like: {"id":"15129"} Before this change, the rendered field could become: {"customfield_11209":{"id":15129}} In that form, id was sent as an integer, which Jira rejected. With this change, quoted numeric values are preserved as strings, so the generated payload keeps the expected type. A regression test was added to cover this case. --------- Signed-off-by: Holger Waschke <holger.waschke@dvag.com> Signed-off-by: Holger Waschke <waschkester@gmail.com> Signed-off-by: Holger Waschke <85643002+holger-waschke@users.noreply.github.com> Co-authored-by: Siavash Safi <git@hosted.run>
1 parent 9e6c8b7 commit 027f11a

3 files changed

Lines changed: 67 additions & 1 deletion

File tree

notify/jira/jira_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,22 @@ func TestJiraTemplating(t *testing.T) {
374374
expectedFieldKey: "customfield_14400",
375375
expectedFieldValue: "host1.example.com",
376376
},
377+
{
378+
title: "numeric string rendered with toJson stays string",
379+
cfg: &JiraConfig{
380+
Summary: JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
381+
Description: JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
382+
Fields: map[string]any{
383+
"customfield_14400": map[string]any{
384+
"id": `{{ "1234" | toJson }}`,
385+
},
386+
},
387+
},
388+
expectedFieldKey: "customfield_14400",
389+
expectedFieldValue: map[string]any{
390+
"id": "1234",
391+
},
392+
},
377393
{
378394
title: "template project",
379395
cfg: &JiraConfig{

template/template.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,10 +652,19 @@ func DeepCopyWithTemplate(value any, tmplTextFunc TemplateFunc) (any, error) {
652652
if ok == nil {
653653
var inlineType any
654654
err := yaml.Unmarshal([]byte(parsed), &inlineType)
655-
if err != nil || (inlineType != nil && reflect.TypeOf(inlineType).Kind() == reflect.String) {
655+
if err != nil {
656656
// ignore error, thus the string is not an interface
657657
return parsed, ok
658658
}
659+
if inlineString, isString := inlineType.(string); isString {
660+
// Decode an explicit JSON string, such as output from toJson.
661+
// Preserve other strings because YAML can remove comments,
662+
// whitespace, and line breaks from plain scalar values.
663+
if json.Valid([]byte(parsed)) {
664+
return inlineString, ok
665+
}
666+
return parsed, ok
667+
}
659668
// inlineType holds structured data decoded from the rendered string.
660669
// This is already final data, so only normalize it into JSON-compatible
661670
// types. It must not be passed back through DeepCopyWithTemplate, because

template/template_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,47 @@ func TestDeepCopyWithTemplate(t *testing.T) {
970970
fn: withSuffix,
971971
want: "hello-templated",
972972
},
973+
{
974+
title: "quoted numeric string stays string",
975+
input: "hello",
976+
fn: TemplateFunc(func(string) (string, error) {
977+
return "\"123\"", nil
978+
}),
979+
want: "123",
980+
},
981+
{
982+
title: "plain string containing YAML comment syntax stays unchanged",
983+
input: "issue # 1234",
984+
fn: identity,
985+
want: "issue # 1234",
986+
},
987+
{
988+
title: "numeric value is rendered as integer",
989+
input: "hello",
990+
fn: TemplateFunc(func(string) (string, error) {
991+
return "1234", nil
992+
}),
993+
want: 1234,
994+
},
995+
{
996+
title: "quoted numeric string stays string in nested map",
997+
input: map[string]any{
998+
"customfield_11209": map[string]any{
999+
"id": "TOKEN",
1000+
},
1001+
},
1002+
fn: TemplateFunc(func(s string) (string, error) {
1003+
if s == "TOKEN" {
1004+
return "\"15129\"", nil
1005+
}
1006+
return s, nil
1007+
}),
1008+
want: map[string]any{
1009+
"customfield_11209": map[string]any{
1010+
"id": "15129",
1011+
},
1012+
},
1013+
},
9731014
{
9741015
title: "string parsed as YAML map",
9751016
input: "foo: bar",

0 commit comments

Comments
 (0)