From 1766c998b28770914b1b1d8bba263688c5884706 Mon Sep 17 00:00:00 2001 From: Haytham Abuelfutuh Date: Wed, 19 May 2021 17:00:38 -0700 Subject: [PATCH] config: Support converting string configs to []byte (#85) * config: Support converting string configs to []byte Signed-off-by: Haytham Abuelfutuh * Use base64 encoding to match yaml.unmarshal behavior Signed-off-by: Haytham Abuelfutuh * support string array since it seems mapstructure does that Signed-off-by: Haytham Abuelfutuh * Fix unit test value Signed-off-by: Haytham Abuelfutuh * Increase patch coverage Signed-off-by: Haytham Abuelfutuh --- flytestdlib/config/tests/testdata/config.yaml | 1 + flytestdlib/config/tests/types_test.go | 1 + flytestdlib/config/viper/viper.go | 31 +++++++++++ flytestdlib/config/viper/viper_test.go | 54 +++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 flytestdlib/config/viper/viper_test.go diff --git a/flytestdlib/config/tests/testdata/config.yaml b/flytestdlib/config/tests/testdata/config.yaml index ca78698fae..9840a8696f 100755 --- a/flytestdlib/config/tests/testdata/config.yaml +++ b/flytestdlib/config/tests/testdata/config.yaml @@ -9,3 +9,4 @@ other-component: - world - '!' url-value: http://something.com + myByteArray: JDJhJDA2JHB4czFBa0c4MUt2cmhwbWwxUWlMU09RYVRrOWVlUHJVLzdZYWI5eTA3aDN4MFRnbGJhb1Q2 diff --git a/flytestdlib/config/tests/types_test.go b/flytestdlib/config/tests/types_test.go index 4d2303dcec..179ca61c83 100644 --- a/flytestdlib/config/tests/types_test.go +++ b/flytestdlib/config/tests/types_test.go @@ -25,6 +25,7 @@ type OtherComponentConfig struct { IntValue int `json:"int-val"` StringArray []string `json:"strings"` StringArrayWithDefaults []string `json:"strings-def"` + MyByteArray []byte `json:"myByteArray"` } type Item struct { diff --git a/flytestdlib/config/viper/viper.go b/flytestdlib/config/viper/viper.go index 78fcc8be5f..526516d57d 100644 --- a/flytestdlib/config/viper/viper.go +++ b/flytestdlib/config/viper/viper.go @@ -2,6 +2,7 @@ package viper import ( "context" + "encoding/base64" "encoding/json" "flag" "fmt" @@ -193,6 +194,35 @@ func sliceToMapHook(f reflect.Kind, t reflect.Kind, data interface{}) (interface return data, nil } +// stringToByteArray allows the conversion from strings to []byte. mapstructure's default behavior involve converting +// each element as a uint8 before assembling the final []byte. +func stringToByteArray(f, t reflect.Type, data interface{}) (interface{}, error) { + // Only handle string -> []byte conversion + if t.Kind() != reflect.Slice || t.Elem().Kind() != reflect.Uint8 { + return data, nil + } + + asStr := "" + if f.Kind() == reflect.String { + asStr = data.(string) + } else if f.Kind() == reflect.Slice && f.Elem().Kind() == reflect.String { + asSlice := data.([]string) + if len(asSlice) == 0 { + return data, nil + } + + asStr = asSlice[0] + } + + b := make([]byte, base64.StdEncoding.DecodedLen(len(asStr))) + n, err := base64.StdEncoding.Decode(b, []byte(asStr)) + if err != nil { + return nil, err + } + + return b[:n], nil +} + // This decoder hook tests types for json unmarshaling capability. If implemented, it uses json unmarshal to build the // object. Otherwise, it'll just pass on the original data. func jsonUnmarshallerHook(_, to reflect.Type, data interface{}) (interface{}, error) { @@ -298,6 +328,7 @@ func defaultDecoderConfig(output interface{}, opts ...viperLib.DecoderConfigOpti mapstructure.StringToTimeDurationHookFunc(), mapstructure.StringToSliceHookFunc(","), sliceToMapHook, + stringToByteArray, ), // Empty/zero fields before applying provided values. This avoids potentially undesired/unexpected merging logic. ZeroFields: true, diff --git a/flytestdlib/config/viper/viper_test.go b/flytestdlib/config/viper/viper_test.go new file mode 100644 index 0000000000..881d4436f2 --- /dev/null +++ b/flytestdlib/config/viper/viper_test.go @@ -0,0 +1,54 @@ +package viper + +import ( + "encoding/base64" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_stringToByteArray(t *testing.T) { + t.Run("Expected types", func(t *testing.T) { + input := "hello world" + base64Encoded := base64.StdEncoding.EncodeToString([]byte(input)) + res, err := stringToByteArray(reflect.TypeOf(base64Encoded), reflect.TypeOf([]byte{}), base64Encoded) + assert.NoError(t, err) + assert.Equal(t, []byte(input), res) + }) + + t.Run("Expected types - array string", func(t *testing.T) { + input := []string{"hello world"} + base64Encoded := base64.StdEncoding.EncodeToString([]byte(input[0])) + res, err := stringToByteArray(reflect.TypeOf(input), reflect.TypeOf([]byte{}), []string{base64Encoded}) + assert.NoError(t, err) + assert.Equal(t, []byte(input[0]), res) + }) + + t.Run("Expected types - invalid encoding", func(t *testing.T) { + input := []string{"hello world"} + _, err := stringToByteArray(reflect.TypeOf(input), reflect.TypeOf([]byte{}), []string{"invalid base64"}) + assert.Error(t, err) + }) + + t.Run("Expected types - empty array string", func(t *testing.T) { + input := []string{"hello world"} + res, err := stringToByteArray(reflect.TypeOf(input), reflect.TypeOf([]byte{}), []string{}) + assert.NoError(t, err) + assert.Equal(t, []string{}, res) + }) + + t.Run("Unexpected types", func(t *testing.T) { + input := 5 + res, err := stringToByteArray(reflect.TypeOf(input), reflect.TypeOf([]byte{}), input) + assert.NoError(t, err) + assert.NotEqual(t, []byte("hello"), res) + }) + + t.Run("Unexpected types", func(t *testing.T) { + input := 5 + res, err := stringToByteArray(reflect.TypeOf(input), reflect.TypeOf(""), input) + assert.NoError(t, err) + assert.NotEqual(t, []byte("hello"), res) + }) +}