From f98411d629ceefd91d6d2e2fe5f3e5cf15fa8730 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Thu, 20 Jun 2024 14:35:53 +0200 Subject: [PATCH 1/2] feat(encoding): use external encoding libraries Signed-off-by: Mark Sagi-Kazar --- go.mod | 6 ++++++ go.sum | 12 ++++++++++++ viper.go | 12 ++++++------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 50ff1a3d8..8806a7a36 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,12 @@ go 1.21 require ( github.com/fsnotify/fsnotify v1.7.0 + github.com/go-viper/encoding/dotenv v0.1.0 + github.com/go-viper/encoding/hcl v0.1.0 + github.com/go-viper/encoding/ini v0.1.0 + github.com/go-viper/encoding/javaproperties v0.1.0 + github.com/go-viper/encoding/toml v0.1.0 + github.com/go-viper/encoding/yaml v0.1.0 github.com/go-viper/mapstructure/v2 v2.0.0 github.com/hashicorp/hcl v1.0.0 github.com/magiconair/properties v1.8.7 diff --git a/go.sum b/go.sum index da9f1f046..8fba0417c 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,18 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-viper/encoding/dotenv v0.1.0 h1:XelRj/HPBriYmnXY59h1QLMBnmWC21qFojzr9vdzbZo= +github.com/go-viper/encoding/dotenv v0.1.0/go.mod h1:SF1B9MODzZ1OLBEePSrjMkJjVg821fFiYea5AIL8CBc= +github.com/go-viper/encoding/hcl v0.1.0 h1:eC0Vo0XLTkWMd0ehSeF4H2ushiKu/lwNAzYnStgTkho= +github.com/go-viper/encoding/hcl v0.1.0/go.mod h1:uXPhzJnVyTb45tuW8lqhcUDe7DYvrZFoZs0HU9vXOyg= +github.com/go-viper/encoding/ini v0.1.0 h1:zzt54JVGIAFGIPYzqauMgJt7gjh9hVC3S+NzWJPvb9Y= +github.com/go-viper/encoding/ini v0.1.0/go.mod h1:Pfi4M2V1eAGJVZ5q6FrkHPhtHED2YgLlXhvgMVrB+YQ= +github.com/go-viper/encoding/javaproperties v0.1.0 h1:4pQN/pez/rMy9ITZ++SgLH6VIN3zWzNNuWFHKjrpn6w= +github.com/go-viper/encoding/javaproperties v0.1.0/go.mod h1:LGaThjx5J/GFdQRJscxLMQsYt0XKAM7IW9YzsJTv6jw= +github.com/go-viper/encoding/toml v0.1.0 h1:Gtd343g0JR9lLeBaudZILjLPYiGmzyE0LSpTf6HsYDU= +github.com/go-viper/encoding/toml v0.1.0/go.mod h1:+gJiMxUZ/wslHYy06NBtkotxZ7fbue+36KvdMm2OXTk= +github.com/go-viper/encoding/yaml v0.1.0 h1:tILva5u4EUgtJDYhAB7U+aj5zIDZqmq5qvcS8U4bFQE= +github.com/go-viper/encoding/yaml v0.1.0/go.mod h1:nsYhB8GzJ0rVF6sA0Xiok0mEuHtmHqmCn5tUuKKAwv0= github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= diff --git a/viper.go b/viper.go index 30ae4aa9e..22314be42 100644 --- a/viper.go +++ b/viper.go @@ -35,19 +35,19 @@ import ( "time" "github.com/fsnotify/fsnotify" + "github.com/go-viper/encoding/dotenv" + "github.com/go-viper/encoding/hcl" + "github.com/go-viper/encoding/ini" + "github.com/go-viper/encoding/javaproperties" + "github.com/go-viper/encoding/toml" + "github.com/go-viper/encoding/yaml" "github.com/go-viper/mapstructure/v2" "github.com/spf13/afero" "github.com/spf13/cast" "github.com/spf13/pflag" "github.com/spf13/viper/internal/encoding" - "github.com/spf13/viper/internal/encoding/dotenv" - "github.com/spf13/viper/internal/encoding/hcl" - "github.com/spf13/viper/internal/encoding/ini" - "github.com/spf13/viper/internal/encoding/javaproperties" "github.com/spf13/viper/internal/encoding/json" - "github.com/spf13/viper/internal/encoding/toml" - "github.com/spf13/viper/internal/encoding/yaml" "github.com/spf13/viper/internal/features" ) From 517cbc6315a984d91962fe47ac3558e432013e15 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Thu, 20 Jun 2024 14:38:01 +0200 Subject: [PATCH 2/2] refactor(encoding): remove external encoding libraries Signed-off-by: Mark Sagi-Kazar --- go.mod | 12 +- internal/encoding/dotenv/codec.go | 61 -------- internal/encoding/dotenv/codec_test.go | 55 -------- internal/encoding/dotenv/map_utils.go | 41 ------ internal/encoding/hcl/codec.go | 40 ------ internal/encoding/hcl/codec_test.go | 132 ------------------ internal/encoding/ini/codec.go | 99 ------------- internal/encoding/ini/codec_test.go | 99 ------------- internal/encoding/ini/map_utils.go | 74 ---------- internal/encoding/javaproperties/codec.go | 86 ------------ .../encoding/javaproperties/codec_test.go | 75 ---------- internal/encoding/javaproperties/map_utils.go | 74 ---------- internal/encoding/toml/codec.go | 16 --- internal/encoding/toml/codec_test.go | 97 ------------- internal/encoding/yaml/codec.go | 14 -- internal/encoding/yaml/codec_test.go | 128 ----------------- 16 files changed, 6 insertions(+), 1097 deletions(-) delete mode 100644 internal/encoding/dotenv/codec.go delete mode 100644 internal/encoding/dotenv/codec_test.go delete mode 100644 internal/encoding/dotenv/map_utils.go delete mode 100644 internal/encoding/hcl/codec.go delete mode 100644 internal/encoding/hcl/codec_test.go delete mode 100644 internal/encoding/ini/codec.go delete mode 100644 internal/encoding/ini/codec_test.go delete mode 100644 internal/encoding/ini/map_utils.go delete mode 100644 internal/encoding/javaproperties/codec.go delete mode 100644 internal/encoding/javaproperties/codec_test.go delete mode 100644 internal/encoding/javaproperties/map_utils.go delete mode 100644 internal/encoding/toml/codec.go delete mode 100644 internal/encoding/toml/codec_test.go delete mode 100644 internal/encoding/yaml/codec.go delete mode 100644 internal/encoding/yaml/codec_test.go diff --git a/go.mod b/go.mod index 8806a7a36..3ba7b0619 100644 --- a/go.mod +++ b/go.mod @@ -11,26 +11,26 @@ require ( github.com/go-viper/encoding/toml v0.1.0 github.com/go-viper/encoding/yaml v0.1.0 github.com/go-viper/mapstructure/v2 v2.0.0 - github.com/hashicorp/hcl v1.0.0 - github.com/magiconair/properties v1.8.7 - github.com/pelletier/go-toml/v2 v2.2.2 github.com/sagikazarmark/locafero v0.6.0 github.com/spf13/afero v1.11.0 github.com/spf13/cast v1.6.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 - github.com/subosito/gotenv v1.6.0 - gopkg.in/ini.v1 v1.67.0 - gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/encoding/dotenv/codec.go b/internal/encoding/dotenv/codec.go deleted file mode 100644 index 3ebc76f02..000000000 --- a/internal/encoding/dotenv/codec.go +++ /dev/null @@ -1,61 +0,0 @@ -package dotenv - -import ( - "bytes" - "fmt" - "sort" - "strings" - - "github.com/subosito/gotenv" -) - -const keyDelimiter = "_" - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables -// (commonly called as dotenv format). -type Codec struct{} - -func (Codec) Encode(v map[string]any) ([]byte, error) { - flattened := map[string]any{} - - flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter) - - keys := make([]string, 0, len(flattened)) - - for key := range flattened { - keys = append(keys, key) - } - - sort.Strings(keys) - - var buf bytes.Buffer - - for _, key := range keys { - _, err := buf.WriteString(fmt.Sprintf("%v=%v\n", strings.ToUpper(key), flattened[key])) - if err != nil { - return nil, err - } - } - - return buf.Bytes(), nil -} - -func (Codec) Decode(b []byte, v map[string]any) error { - var buf bytes.Buffer - - _, err := buf.Write(b) - if err != nil { - return err - } - - env, err := gotenv.StrictParse(&buf) - if err != nil { - return err - } - - for key, value := range env { - v[key] = value - } - - return nil -} diff --git a/internal/encoding/dotenv/codec_test.go b/internal/encoding/dotenv/codec_test.go deleted file mode 100644 index ac2257b79..000000000 --- a/internal/encoding/dotenv/codec_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package dotenv - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `# key-value pair -KEY=value -` - -// encoded form of the data. -const encoded = `KEY=value -` - -// data is Viper's internal representation. -var data = map[string]any{ - "KEY": "value", -} - -func TestCodec_Encode(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, data, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(`invalid data`), v) - require.Error(t, err) - - t.Logf("decoding failed as expected: %s", err) - }) -} diff --git a/internal/encoding/dotenv/map_utils.go b/internal/encoding/dotenv/map_utils.go deleted file mode 100644 index 8bfe0a9de..000000000 --- a/internal/encoding/dotenv/map_utils.go +++ /dev/null @@ -1,41 +0,0 @@ -package dotenv - -import ( - "strings" - - "github.com/spf13/cast" -) - -// flattenAndMergeMap recursively flattens the given map into a new map -// Code is based on the function with the same name in the main package. -// TODO: move it to a common place. -func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any { - if shadow != nil && prefix != "" && shadow[prefix] != nil { - // prefix is shadowed => nothing more to flatten - return shadow - } - if shadow == nil { - shadow = make(map[string]any) - } - - var m2 map[string]any - if prefix != "" { - prefix += delimiter - } - for k, val := range m { - fullKey := prefix + k - switch val := val.(type) { - case map[string]any: - m2 = val - case map[any]any: - m2 = cast.ToStringMap(val) - default: - // immediate value - shadow[strings.ToLower(fullKey)] = val - continue - } - // recursively merge to shadow map - shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) - } - return shadow -} diff --git a/internal/encoding/hcl/codec.go b/internal/encoding/hcl/codec.go deleted file mode 100644 index d7fa8a1b7..000000000 --- a/internal/encoding/hcl/codec.go +++ /dev/null @@ -1,40 +0,0 @@ -package hcl - -import ( - "bytes" - "encoding/json" - - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/printer" -) - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for HCL encoding. -// TODO: add printer config to the codec? -type Codec struct{} - -func (Codec) Encode(v map[string]any) ([]byte, error) { - b, err := json.Marshal(v) - if err != nil { - return nil, err - } - - // TODO: use printer.Format? Is the trailing newline an issue? - - ast, err := hcl.Parse(string(b)) - if err != nil { - return nil, err - } - - var buf bytes.Buffer - - err = printer.Fprint(&buf, ast.Node) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (Codec) Decode(b []byte, v map[string]any) error { - return hcl.Unmarshal(b, &v) -} diff --git a/internal/encoding/hcl/codec_test.go b/internal/encoding/hcl/codec_test.go deleted file mode 100644 index b1d2d5a8a..000000000 --- a/internal/encoding/hcl/codec_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package hcl - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `# key-value pair -"key" = "value" - -// list -"list" = ["item1", "item2", "item3"] - -/* map */ -"map" = { - "key" = "value" -} - -/* -nested map -*/ -"nested_map" "map" { - "key" = "value" - - "list" = ["item1", "item2", "item3"] -}` - -// encoded form of the data. -const encoded = `"key" = "value" - -"list" = ["item1", "item2", "item3"] - -"map" = { - "key" = "value" -} - -"nested_map" "map" { - "key" = "value" - - "list" = ["item1", "item2", "item3"] -}` - -// decoded form of the data. -// -// In case of HCL it's slightly different from Viper's internal representation -// (e.g. map is decoded into a list of maps). -var decoded = map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - "map": []map[string]any{ - { - "key": "value", - }, - }, - "nested_map": []map[string]any{ - { - "map": []map[string]any{ - { - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - }, - }, - }, - }, -} - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - "map": map[string]any{ - "key": "value", - }, - "nested_map": map[string]any{ - "map": map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - }, - }, -} - -func TestCodec_Encode(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, decoded, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(`invalid data`), v) - require.Error(t, err) - - t.Logf("decoding failed as expected: %s", err) - }) -} diff --git a/internal/encoding/ini/codec.go b/internal/encoding/ini/codec.go deleted file mode 100644 index d91cf59d2..000000000 --- a/internal/encoding/ini/codec.go +++ /dev/null @@ -1,99 +0,0 @@ -package ini - -import ( - "bytes" - "sort" - "strings" - - "github.com/spf13/cast" - "gopkg.in/ini.v1" -) - -// LoadOptions contains all customized options used for load data source(s). -// This type is added here for convenience: this way consumers can import a single package called "ini". -type LoadOptions = ini.LoadOptions - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding. -type Codec struct { - KeyDelimiter string - LoadOptions LoadOptions -} - -func (c Codec) Encode(v map[string]any) ([]byte, error) { - cfg := ini.Empty() - ini.PrettyFormat = false - - flattened := map[string]any{} - - flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter()) - - keys := make([]string, 0, len(flattened)) - - for key := range flattened { - keys = append(keys, key) - } - - sort.Strings(keys) - - for _, key := range keys { - sectionName, keyName := "", key - - lastSep := strings.LastIndex(key, ".") - if lastSep != -1 { - sectionName = key[:(lastSep)] - keyName = key[(lastSep + 1):] - } - - // TODO: is this a good idea? - if sectionName == "default" { - sectionName = "" - } - - cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key])) - } - - var buf bytes.Buffer - - _, err := cfg.WriteTo(&buf) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c Codec) Decode(b []byte, v map[string]any) error { - cfg := ini.Empty(c.LoadOptions) - - err := cfg.Append(b) - if err != nil { - return err - } - - sections := cfg.Sections() - - for i := 0; i < len(sections); i++ { - section := sections[i] - keys := section.Keys() - - for j := 0; j < len(keys); j++ { - key := keys[j] - value := cfg.Section(section.Name()).Key(key.Name()).String() - - deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter())) - - // set innermost value - deepestMap[key.Name()] = value - } - } - - return nil -} - -func (c Codec) keyDelimiter() string { - if c.KeyDelimiter == "" { - return "." - } - - return c.KeyDelimiter -} diff --git a/internal/encoding/ini/codec_test.go b/internal/encoding/ini/codec_test.go deleted file mode 100644 index f9d9e7019..000000000 --- a/internal/encoding/ini/codec_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package ini - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `; key-value pair -key=value ; key-value pair - -# map -[map] # map -key=%(key)s - -` - -// encoded form of the data. -const encoded = `key=value - -[map] -key=value -` - -// decoded form of the data. -// -// In case of INI it's slightly different from Viper's internal representation -// (e.g. top level keys land in a section called default). -var decoded = map[string]any{ - "DEFAULT": map[string]any{ - "key": "value", - }, - "map": map[string]any{ - "key": "value", - }, -} - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "map": map[string]any{ - "key": "value", - }, -} - -func TestCodec_Encode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) - }) - - t.Run("Default", func(t *testing.T) { - codec := Codec{} - - data := map[string]any{ - "default": map[string]any{ - "key": "value", - }, - "map": map[string]any{ - "key": "value", - }, - } - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) - }) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, decoded, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(`invalid data`), v) - require.Error(t, err) - - t.Logf("decoding failed as expected: %s", err) - }) -} diff --git a/internal/encoding/ini/map_utils.go b/internal/encoding/ini/map_utils.go deleted file mode 100644 index 490ab594e..000000000 --- a/internal/encoding/ini/map_utils.go +++ /dev/null @@ -1,74 +0,0 @@ -package ini - -import ( - "strings" - - "github.com/spf13/cast" -) - -// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED -// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE -// deepSearch scans deep maps, following the key indexes listed in the -// sequence "path". -// The last value is expected to be another map, and is returned. -// -// In case intermediate keys do not exist, or map to a non-map value, -// a new map is created and inserted, and the search continues from there: -// the initial map "m" may be modified! -func deepSearch(m map[string]any, path []string) map[string]any { - for _, k := range path { - m2, ok := m[k] - if !ok { - // intermediate key does not exist - // => create it and continue from there - m3 := make(map[string]any) - m[k] = m3 - m = m3 - continue - } - m3, ok := m2.(map[string]any) - if !ok { - // intermediate key is a value - // => replace with a new map - m3 = make(map[string]any) - m[k] = m3 - } - // continue search from here - m = m3 - } - return m -} - -// flattenAndMergeMap recursively flattens the given map into a new map -// Code is based on the function with the same name in the main package. -// TODO: move it to a common place. -func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any { - if shadow != nil && prefix != "" && shadow[prefix] != nil { - // prefix is shadowed => nothing more to flatten - return shadow - } - if shadow == nil { - shadow = make(map[string]any) - } - - var m2 map[string]any - if prefix != "" { - prefix += delimiter - } - for k, val := range m { - fullKey := prefix + k - switch val := val.(type) { - case map[string]any: - m2 = val - case map[any]any: - m2 = cast.ToStringMap(val) - default: - // immediate value - shadow[strings.ToLower(fullKey)] = val - continue - } - // recursively merge to shadow map - shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) - } - return shadow -} diff --git a/internal/encoding/javaproperties/codec.go b/internal/encoding/javaproperties/codec.go deleted file mode 100644 index e92e5172c..000000000 --- a/internal/encoding/javaproperties/codec.go +++ /dev/null @@ -1,86 +0,0 @@ -package javaproperties - -import ( - "bytes" - "sort" - "strings" - - "github.com/magiconair/properties" - "github.com/spf13/cast" -) - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding. -type Codec struct { - KeyDelimiter string - - // Store read properties on the object so that we can write back in order with comments. - // This will only be used if the configuration read is a properties file. - // TODO: drop this feature in v2 - // TODO: make use of the global properties object optional - Properties *properties.Properties -} - -func (c *Codec) Encode(v map[string]any) ([]byte, error) { - if c.Properties == nil { - c.Properties = properties.NewProperties() - } - - flattened := map[string]any{} - - flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter()) - - keys := make([]string, 0, len(flattened)) - - for key := range flattened { - keys = append(keys, key) - } - - sort.Strings(keys) - - for _, key := range keys { - _, _, err := c.Properties.Set(key, cast.ToString(flattened[key])) - if err != nil { - return nil, err - } - } - - var buf bytes.Buffer - - _, err := c.Properties.WriteComment(&buf, "#", properties.UTF8) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c *Codec) Decode(b []byte, v map[string]any) error { - var err error - c.Properties, err = properties.Load(b, properties.UTF8) - if err != nil { - return err - } - - for _, key := range c.Properties.Keys() { - // ignore existence check: we know it's there - value, _ := c.Properties.Get(key) - - // recursively build nested maps - path := strings.Split(key, c.keyDelimiter()) - lastKey := strings.ToLower(path[len(path)-1]) - deepestMap := deepSearch(v, path[0:len(path)-1]) - - // set innermost value - deepestMap[lastKey] = value - } - - return nil -} - -func (c Codec) keyDelimiter() string { - if c.KeyDelimiter == "" { - return "." - } - - return c.KeyDelimiter -} diff --git a/internal/encoding/javaproperties/codec_test.go b/internal/encoding/javaproperties/codec_test.go deleted file mode 100644 index ab5aec0b5..000000000 --- a/internal/encoding/javaproperties/codec_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package javaproperties - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `#key-value pair -key = value -map.key = value -` - -// encoded form of the data. -const encoded = `key = value -map.key = value -` - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "map": map[string]any{ - "key": "value", - }, -} - -func TestCodec_Encode(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, data, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - t.Skip("TODO: needs invalid data example") - - codec := Codec{} - - v := map[string]any{} - - codec.Decode([]byte(``), v) - - assert.Empty(t, v) - }) -} - -func TestCodec_DecodeEncode(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, original, string(b)) -} diff --git a/internal/encoding/javaproperties/map_utils.go b/internal/encoding/javaproperties/map_utils.go deleted file mode 100644 index 6e1aff223..000000000 --- a/internal/encoding/javaproperties/map_utils.go +++ /dev/null @@ -1,74 +0,0 @@ -package javaproperties - -import ( - "strings" - - "github.com/spf13/cast" -) - -// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED -// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE -// deepSearch scans deep maps, following the key indexes listed in the -// sequence "path". -// The last value is expected to be another map, and is returned. -// -// In case intermediate keys do not exist, or map to a non-map value, -// a new map is created and inserted, and the search continues from there: -// the initial map "m" may be modified! -func deepSearch(m map[string]any, path []string) map[string]any { - for _, k := range path { - m2, ok := m[k] - if !ok { - // intermediate key does not exist - // => create it and continue from there - m3 := make(map[string]any) - m[k] = m3 - m = m3 - continue - } - m3, ok := m2.(map[string]any) - if !ok { - // intermediate key is a value - // => replace with a new map - m3 = make(map[string]any) - m[k] = m3 - } - // continue search from here - m = m3 - } - return m -} - -// flattenAndMergeMap recursively flattens the given map into a new map -// Code is based on the function with the same name in the main package. -// TODO: move it to a common place. -func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any { - if shadow != nil && prefix != "" && shadow[prefix] != nil { - // prefix is shadowed => nothing more to flatten - return shadow - } - if shadow == nil { - shadow = make(map[string]any) - } - - var m2 map[string]any - if prefix != "" { - prefix += delimiter - } - for k, val := range m { - fullKey := prefix + k - switch val := val.(type) { - case map[string]any: - m2 = val - case map[any]any: - m2 = cast.ToStringMap(val) - default: - // immediate value - shadow[strings.ToLower(fullKey)] = val - continue - } - // recursively merge to shadow map - shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) - } - return shadow -} diff --git a/internal/encoding/toml/codec.go b/internal/encoding/toml/codec.go deleted file mode 100644 index c70aa8d28..000000000 --- a/internal/encoding/toml/codec.go +++ /dev/null @@ -1,16 +0,0 @@ -package toml - -import ( - "github.com/pelletier/go-toml/v2" -) - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding. -type Codec struct{} - -func (Codec) Encode(v map[string]any) ([]byte, error) { - return toml.Marshal(v) -} - -func (Codec) Decode(b []byte, v map[string]any) error { - return toml.Unmarshal(b, &v) -} diff --git a/internal/encoding/toml/codec_test.go b/internal/encoding/toml/codec_test.go deleted file mode 100644 index 91d9e130c..000000000 --- a/internal/encoding/toml/codec_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package toml - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `# key-value pair -key = "value" -list = ["item1", "item2", "item3"] - -[map] -key = "value" - -# nested -# map -[nested_map] -[nested_map.map] -key = "value" -list = [ - "item1", - "item2", - "item3", -] -` - -// encoded form of the data. -const encoded = `key = 'value' -list = ['item1', 'item2', 'item3'] - -[map] -key = 'value' - -[nested_map] -[nested_map.map] -key = 'value' -list = ['item1', 'item2', 'item3'] -` - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - "map": map[string]any{ - "key": "value", - }, - "nested_map": map[string]any{ - "map": map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - }, - }, -} - -func TestCodec_Encode(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, data, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(`invalid data`), v) - require.Error(t, err) - - t.Logf("decoding failed as expected: %s", err) - }) -} diff --git a/internal/encoding/yaml/codec.go b/internal/encoding/yaml/codec.go deleted file mode 100644 index 036879249..000000000 --- a/internal/encoding/yaml/codec.go +++ /dev/null @@ -1,14 +0,0 @@ -package yaml - -import "gopkg.in/yaml.v3" - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding. -type Codec struct{} - -func (Codec) Encode(v map[string]any) ([]byte, error) { - return yaml.Marshal(v) -} - -func (Codec) Decode(b []byte, v map[string]any) error { - return yaml.Unmarshal(b, &v) -} diff --git a/internal/encoding/yaml/codec_test.go b/internal/encoding/yaml/codec_test.go deleted file mode 100644 index 2d2d8ba6a..000000000 --- a/internal/encoding/yaml/codec_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package yaml - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `# key-value pair -key: value -list: - - item1 - - item2 - - item3 -map: - key: value - -# nested -# map -nested_map: - map: - key: value - list: - - item1 - - item2 - - item3 -` - -// encoded form of the data. -const encoded = `key: value -list: - - item1 - - item2 - - item3 -map: - key: value -nested_map: - map: - key: value - list: - - item1 - - item2 - - item3 -` - -// decoded form of the data. -// -// In case of YAML it's slightly different from Viper's internal representation -// (e.g. map is decoded into a map with interface key). -var decoded = map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - "map": map[string]any{ - "key": "value", - }, - "nested_map": map[string]any{ - "map": map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - }, - }, -} - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - "map": map[string]any{ - "key": "value", - }, - "nested_map": map[string]any{ - "map": map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - }, - }, -} - -func TestCodec_Encode(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, decoded, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(`invalid data`), v) - require.Error(t, err) - - t.Logf("decoding failed as expected: %s", err) - }) -}