From dbae12809424f03df822c325899aceaeb5d3b999 Mon Sep 17 00:00:00 2001 From: Dennis Klipp Date: Tue, 4 Aug 2026 20:29:57 +0200 Subject: [PATCH] Templates: keep large numeric params parseable (#32528) Co-authored-by: Dennis Klipp <6085348+DennisKlipp@users.noreply.github.com> --- util/templates/formatvalue_test.go | 38 ++++++++++++++++++++++++++++++ util/templates/template.go | 18 +++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 util/templates/formatvalue_test.go diff --git a/util/templates/formatvalue_test.go b/util/templates/formatvalue_test.go new file mode 100644 index 000000000..0e0c34493 --- /dev/null +++ b/util/templates/formatvalue_test.go @@ -0,0 +1,38 @@ +package templates + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// JSON numbers arrive as float64. Large integers must not be rendered in +// exponential notation, which would no longer parse as an integer. +func TestFormatValue(t *testing.T) { + for _, tc := range []struct { + in any + want string + }{ + {float64(3), "3"}, + {float64(8899), "8899"}, + {float64(3493601102), "3493601102"}, // ten digit device serial + {float64(1.5), "1.5"}, + {int(42), "42"}, + {"text", "text"}, + } { + assert.Equal(t, tc.want, formatValue(tc.in)) + } +} + +// end-to-end: a required int param supplied as a JSON number must satisfy the +// required check, e.g. a ten digit device serial +func TestRequiredLargeNumber(t *testing.T) { + tmpl := &Template{ + Params: []Param{{Name: "serial", Type: TypeInt, Required: true}}, + } + + _, _, err := tmpl.RenderResult(RenderModeUnitTest, map[string]any{ + "serial": float64(3493601102), + }) + assert.NoError(t, err, "large serial supplied as JSON number") +} diff --git a/util/templates/template.go b/util/templates/template.go index bd93f93f8..57b04b377 100644 --- a/util/templates/template.go +++ b/util/templates/template.go @@ -274,6 +274,8 @@ func (t *Template) RenderProxyWithValues(values map[string]any, lang string) ([] t.Params[index].Value = p.yamlQuote(v) case int: t.Params[index].Value = strconv.Itoa(v) + case float64, float32: + t.Params[index].Value = formatValue(v) } } } @@ -308,6 +310,20 @@ func (t *Template) RenderProxyWithValues(values map[string]any, lang string) ([] return bytes.TrimSpace(out.Bytes()), err } +// formatValue renders a parameter value for yaml. JSON numbers arrive as float64, +// which %v would render in exponential notation for large values, e.g. a ten digit +// serial number. Those are no longer parseable as integers. +func formatValue(val any) string { + switch v := val.(type) { + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32) + default: + return fmt.Sprintf("%v", val) + } +} + // RenderResult renders the result template to instantiate the proxy func (t *Template) RenderResult(renderMode int, other map[string]any) ([]byte, map[string]any, error) { values := t.Defaults(renderMode) @@ -383,7 +399,7 @@ func (t *Template) RenderResult(renderMode int, other map[string]any) ([]byte, m // prevent rendering nil interfaces as "" string var s string if val != nil { - s = p.yamlQuote(fmt.Sprintf("%v", val)) + s = p.yamlQuote(formatValue(val)) } // validate required fields from yaml