Skip to content

Commit cbea179

Browse files
author
Ciprian Tibulca
authored
CLOUDP-226171: handle safely pointers of slices in the output templates (#2569)
1 parent b628ab9 commit cbea179

File tree

2 files changed

+114
-1
lines changed

2 files changed

+114
-1
lines changed

internal/templatewriter/templatewriter.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package templatewriter
1616

1717
import (
1818
"io"
19+
"reflect"
1920
"text/tabwriter"
2021
"text/template"
2122
)
@@ -27,6 +28,23 @@ const (
2728
tabwriterPadChar = ' '
2829
)
2930

31+
var funcMap = template.FuncMap{
32+
"valueOrEmptySlice": valueOrEmptySlice,
33+
}
34+
35+
func valueOrEmptySlice(slice interface{}) (result interface{}) {
36+
if slice == nil {
37+
return result
38+
}
39+
40+
k := reflect.TypeOf(slice).Kind()
41+
if (k == reflect.Slice || k == reflect.Ptr) && reflect.ValueOf(slice).IsNil() {
42+
return result
43+
}
44+
45+
return slice
46+
}
47+
3048
// newTabWriter returns a tabwriter that handles tabs(`\t) to space columns evenly.
3149
func newTabWriter(output io.Writer) *tabwriter.Writer {
3250
return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, 0)
@@ -37,7 +55,7 @@ func newTabWriter(output io.Writer) *tabwriter.Writer {
3755
// this template will be handled with a tabwriter so you can use tabs (\t)
3856
// and new lines (\n) to space your content evenly.
3957
func Print(writer io.Writer, t string, v interface{}) error {
40-
tmpl, err := template.New("output").Parse(t)
58+
tmpl, err := template.New("output").Funcs(funcMap).Parse(t)
4159
if err != nil {
4260
return err
4361
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2024 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package templatewriter
16+
17+
import (
18+
"bytes"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
type printTest struct {
26+
name string
27+
template string
28+
data interface{}
29+
expected string
30+
wantErr require.ErrorAssertionFunc
31+
}
32+
33+
func Test_Print(t *testing.T) {
34+
var buf bytes.Buffer
35+
36+
tests := []printTest{
37+
{
38+
name: "primitive data",
39+
template: "name: {{.}}",
40+
data: "Jane",
41+
expected: "name: Jane",
42+
wantErr: require.NoError,
43+
},
44+
{
45+
name: "nil data",
46+
template: "name: {{.}}",
47+
data: nil,
48+
expected: "name: <no value>",
49+
wantErr: require.NoError,
50+
},
51+
{
52+
name: "pointer of non empty slice",
53+
template: "items: {{range .Items}}{{.}} {{end}}",
54+
data: struct{ Items *[]string }{Items: &[]string{"AWS", "GCP", "Azure"}},
55+
expected: "items: AWS GCP Azure ",
56+
wantErr: require.NoError,
57+
},
58+
{
59+
name: "nil pointer of slice",
60+
template: "items: {{range .Items}}{{.}} {{end}}",
61+
data: struct{ Items *[]string }{Items: nil},
62+
expected: "",
63+
wantErr: require.Error, // expected to fail, as Items is nil
64+
},
65+
{
66+
name: "nil pointer of slice",
67+
template: "items: {{range valueOrEmptySlice .Items}}{{.}} {{end}}",
68+
data: struct{ Items *[]string }{Items: nil},
69+
expected: "items: ",
70+
wantErr: require.NoError,
71+
},
72+
{
73+
name: "non empty slice",
74+
template: "items: {{range valueOrEmptySlice .Items}}{{.}} {{end}}",
75+
data: struct{ Items []string }{Items: []string{"AWS", "GCP", "Azure"}},
76+
expected: "items: AWS GCP Azure ",
77+
wantErr: require.NoError,
78+
},
79+
{
80+
name: "empty slice",
81+
template: "items: {{range valueOrEmptySlice .Items}}{{.}} {{end}}",
82+
data: struct{ Items []string }{Items: []string{}},
83+
expected: "items: ",
84+
wantErr: require.NoError,
85+
},
86+
}
87+
88+
for _, conf := range tests {
89+
t.Run(conf.name, func(t *testing.T) {
90+
conf.wantErr(t, Print(&buf, conf.template, conf.data))
91+
assert.Equal(t, conf.expected, buf.String())
92+
})
93+
buf.Reset()
94+
}
95+
}

0 commit comments

Comments
 (0)