From cbea179c78f10a187effdae905d59ade8b769d68 Mon Sep 17 00:00:00 2001 From: Ciprian Tibulca Date: Fri, 26 Jan 2024 18:12:12 +0000 Subject: [PATCH] CLOUDP-226171: handle safely pointers of slices in the output templates (#2569) --- internal/templatewriter/templatewriter.go | 20 +++- .../templatewriter/templatewriter_test.go | 95 +++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 internal/templatewriter/templatewriter_test.go diff --git a/internal/templatewriter/templatewriter.go b/internal/templatewriter/templatewriter.go index d7b3cb2ceb..8f507b2355 100644 --- a/internal/templatewriter/templatewriter.go +++ b/internal/templatewriter/templatewriter.go @@ -16,6 +16,7 @@ package templatewriter import ( "io" + "reflect" "text/tabwriter" "text/template" ) @@ -27,6 +28,23 @@ const ( tabwriterPadChar = ' ' ) +var funcMap = template.FuncMap{ + "valueOrEmptySlice": valueOrEmptySlice, +} + +func valueOrEmptySlice(slice interface{}) (result interface{}) { + if slice == nil { + return result + } + + k := reflect.TypeOf(slice).Kind() + if (k == reflect.Slice || k == reflect.Ptr) && reflect.ValueOf(slice).IsNil() { + return result + } + + return slice +} + // newTabWriter returns a tabwriter that handles tabs(`\t) to space columns evenly. func newTabWriter(output io.Writer) *tabwriter.Writer { return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, 0) @@ -37,7 +55,7 @@ func newTabWriter(output io.Writer) *tabwriter.Writer { // this template will be handled with a tabwriter so you can use tabs (\t) // and new lines (\n) to space your content evenly. func Print(writer io.Writer, t string, v interface{}) error { - tmpl, err := template.New("output").Parse(t) + tmpl, err := template.New("output").Funcs(funcMap).Parse(t) if err != nil { return err } diff --git a/internal/templatewriter/templatewriter_test.go b/internal/templatewriter/templatewriter_test.go new file mode 100644 index 0000000000..0d062c3ce8 --- /dev/null +++ b/internal/templatewriter/templatewriter_test.go @@ -0,0 +1,95 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package templatewriter + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type printTest struct { + name string + template string + data interface{} + expected string + wantErr require.ErrorAssertionFunc +} + +func Test_Print(t *testing.T) { + var buf bytes.Buffer + + tests := []printTest{ + { + name: "primitive data", + template: "name: {{.}}", + data: "Jane", + expected: "name: Jane", + wantErr: require.NoError, + }, + { + name: "nil data", + template: "name: {{.}}", + data: nil, + expected: "name: ", + wantErr: require.NoError, + }, + { + name: "pointer of non empty slice", + template: "items: {{range .Items}}{{.}} {{end}}", + data: struct{ Items *[]string }{Items: &[]string{"AWS", "GCP", "Azure"}}, + expected: "items: AWS GCP Azure ", + wantErr: require.NoError, + }, + { + name: "nil pointer of slice", + template: "items: {{range .Items}}{{.}} {{end}}", + data: struct{ Items *[]string }{Items: nil}, + expected: "", + wantErr: require.Error, // expected to fail, as Items is nil + }, + { + name: "nil pointer of slice", + template: "items: {{range valueOrEmptySlice .Items}}{{.}} {{end}}", + data: struct{ Items *[]string }{Items: nil}, + expected: "items: ", + wantErr: require.NoError, + }, + { + name: "non empty slice", + template: "items: {{range valueOrEmptySlice .Items}}{{.}} {{end}}", + data: struct{ Items []string }{Items: []string{"AWS", "GCP", "Azure"}}, + expected: "items: AWS GCP Azure ", + wantErr: require.NoError, + }, + { + name: "empty slice", + template: "items: {{range valueOrEmptySlice .Items}}{{.}} {{end}}", + data: struct{ Items []string }{Items: []string{}}, + expected: "items: ", + wantErr: require.NoError, + }, + } + + for _, conf := range tests { + t.Run(conf.name, func(t *testing.T) { + conf.wantErr(t, Print(&buf, conf.template, conf.data)) + assert.Equal(t, conf.expected, buf.String()) + }) + buf.Reset() + } +}