diff --git a/utils/cobrautil/bind.go b/utils/cobrautil/bind.go index 0414c038..6d11b177 100644 --- a/utils/cobrautil/bind.go +++ b/utils/cobrautil/bind.go @@ -10,6 +10,7 @@ import ( "fmt" "strings" + "github.com/spf13/cast" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -55,8 +56,8 @@ func BindAll(cmd *cobra.Command, envPrefix, configFileFlagName string) error { ok = true fs.VisitAll(func(f *pflag.Flag) { if !f.Changed && v.IsSet(f.Name) { - if err := setFlagFromViper(f, fs, v.Get(f.Name)); err != nil { - fmt.Fprintln(cmd.ErrOrStderr(), err.Error()) + if err := setFlagFromViper(f, v.Get(f.Name)); err != nil { + fmt.Fprintf(cmd.ErrOrStderr(), "%s: %s\n", f.Name, err) ok = false } } @@ -75,11 +76,22 @@ func BindAll(cmd *cobra.Command, envPrefix, configFileFlagName string) error { return nil } -func setFlagFromViper(f *pflag.Flag, fs *pflag.FlagSet, v any) error { - s := fmt.Sprintf("%v", v) - s = strings.TrimPrefix(s, "[") - s = strings.TrimSuffix(s, "]") - s = strings.NewReplacer(", ", ",", " ", ",").Replace(s) +func setFlagFromViper(f *pflag.Flag, v any) error { + if vs, ok := v.([]interface{}); ok { + sr, ok := f.Value.(sliceReplacer) + if !ok { + return fmt.Errorf("trying to set list to %s", f.Value.Type()) + } + ss, err := cast.ToStringSliceE(vs) + if err != nil { + return err + } + return sr.Replace(ss) + } + + return f.Value.Set(fmt.Sprintf("%v", v)) +} - return fs.Set(f.Name, s) +type sliceReplacer interface { + Replace(val []string) error } diff --git a/utils/cobrautil/bind_test.go b/utils/cobrautil/bind_test.go new file mode 100644 index 00000000..a88656da --- /dev/null +++ b/utils/cobrautil/bind_test.go @@ -0,0 +1,66 @@ +// Copyright 2023 Sauce Labs Inc. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package cobrautil + +import ( + "net/netip" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/mmatczuk/anyflag" + "github.com/spf13/cobra" +) + +type testSliceStruct struct { + Strings []string + Ints []int + Bools []bool + IPs []netip.Addr +} + +func TestBindSlice(t *testing.T) { + formats := []string{ + "yaml", + "json", + "toml", + } + + for _, ext := range formats { + t.Run(ext, func(t *testing.T) { + cmd := &cobra.Command{} + fs := cmd.Flags() + + var v testSliceStruct + fs.String("config-file", "testdata/bind-slice."+ext, "") + fs.StringSliceVar(&v.Strings, "strings", nil, "") + fs.IntSliceVar(&v.Ints, "ints", nil, "") + fs.BoolSliceVar(&v.Bools, "bools", nil, "") + fs.Var(anyflag.NewSliceValue[netip.Addr](nil, &v.IPs, netip.ParseAddr), "ips", "") + + if err := BindAll(cmd, "TEST", "config-file"); err != nil { + t.Fatal(err) + } + + expected := testSliceStruct{ + Strings: []string{"a", "b", "c"}, + Ints: []int{1, 2, 3}, + Bools: []bool{true, false}, + IPs: []netip.Addr{ + netip.MustParseAddr("127.0.0.1"), + netip.MustParseAddr("127.0.0.2"), + }, + } + + ipcmp := cmp.Comparer(func(a, b netip.Addr) bool { + return a.String() == b.String() + }) + if diff := cmp.Diff(expected, v, ipcmp); diff != "" { + t.Fatalf("unexpected result (-want +got):\n%s", diff) + } + }) + } +} diff --git a/utils/cobrautil/testdata/bind-slice.json b/utils/cobrautil/testdata/bind-slice.json new file mode 100644 index 00000000..18df21bc --- /dev/null +++ b/utils/cobrautil/testdata/bind-slice.json @@ -0,0 +1,6 @@ +{ + "strings": ["a", "b", "c"], + "ints": [1, 2, 3], + "bools": [true, false], + "ips": ["127.0.0.1", "127.0.0.2"] +} diff --git a/utils/cobrautil/testdata/bind-slice.toml b/utils/cobrautil/testdata/bind-slice.toml new file mode 100644 index 00000000..6f109700 --- /dev/null +++ b/utils/cobrautil/testdata/bind-slice.toml @@ -0,0 +1,4 @@ +strings = ["a", "b", "c"] +ints = [1, 2, 3] +bools = [true, false] +ips = ["127.0.0.1", "127.0.0.2"] diff --git a/utils/cobrautil/testdata/bind-slice.yaml b/utils/cobrautil/testdata/bind-slice.yaml new file mode 100644 index 00000000..5f3fc7c7 --- /dev/null +++ b/utils/cobrautil/testdata/bind-slice.yaml @@ -0,0 +1,17 @@ +strings: + - a + - b + - c + +ints: + - 1 + - 2 + - 3 + +bools: + - true + - false + +ips: + - 127.0.0.1 + - 127.0.0.2