From 82b4d8357d63fd1988f22170ecffff6406ce2a0b Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 24 Jun 2024 15:10:56 +0200 Subject: [PATCH 1/4] feat(encoding): drop HCL, Java properties and INI Signed-off-by: Mark Sagi-Kazar --- encoding.go | 17 ---- viper_test.go | 257 +++----------------------------------------------- 2 files changed, 15 insertions(+), 259 deletions(-) diff --git a/encoding.go b/encoding.go index b9e090f00..1d6cd6037 100644 --- a/encoding.go +++ b/encoding.go @@ -6,9 +6,6 @@ import ( "sync" "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" @@ -113,20 +110,6 @@ func (r codecRegistry) codec(format string) (Codec, bool) { case "toml": return toml.Codec{}, true - case "hcl", "tfvars": - return hcl.Codec{}, true - - case "ini": - return ini.Codec{ - KeyDelimiter: r.v.keyDelim, - LoadOptions: r.v.iniLoadOptions, - }, true - - case "properties", "props", "prop": // Note: This breaks writing a properties file. - return &javaproperties.Codec{ - KeyDelimiter: v.keyDelim, - }, true - case "dotenv", "env": return &dotenv.Codec{}, true } diff --git a/viper_test.go b/viper_test.go index 05ca0ce73..a4df402af 100644 --- a/viper_test.go +++ b/viper_test.go @@ -84,58 +84,12 @@ var jsonExample = []byte(`{ } }`) -var hclExample = []byte(` -id = "0001" -type = "donut" -name = "Cake" -ppu = 0.55 -foos { - foo { - key = 1 - } - foo { - key = 2 - } - foo { - key = 3 - } - foo { - key = 4 - } -}`) - -var propertiesExample = []byte(` -p_id: 0001 -p_type: donut -p_name: Cake -p_ppu: 0.55 -p_batters.batter.type: Regular -`) - var remoteExample = []byte(`{ "id":"0002", "type":"cronut", "newkey":"remote" }`) -var iniExample = []byte(`; Package name -NAME = ini -; Package version -VERSION = v1 -; Package import path -IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s - -# Information about package author -# Bio can be written in multiple lines. -[author] -NAME = Unknown ; Succeeding comment -E-MAIL = fake@localhost -GITHUB = https://github.com/%(NAME)s -BIO = """Gopher. -Coding addict. -Good man. -""" # Succeeding comment`) - func initConfigs(v *Viper) { var r io.Reader v.SetConfigType("yaml") @@ -146,14 +100,6 @@ func initConfigs(v *Viper) { r = bytes.NewReader(jsonExample) v.unmarshalReader(r, v.config) - v.SetConfigType("hcl") - r = bytes.NewReader(hclExample) - v.unmarshalReader(r, v.config) - - v.SetConfigType("properties") - r = bytes.NewReader(propertiesExample) - v.unmarshalReader(r, v.config) - v.SetConfigType("toml") r = bytes.NewReader(tomlExample) v.unmarshalReader(r, v.config) @@ -165,10 +111,6 @@ func initConfigs(v *Viper) { v.SetConfigType("json") remote := bytes.NewReader(remoteExample) v.unmarshalReader(remote, v.kvstore) - - v.SetConfigType("ini") - r = bytes.NewReader(iniExample) - v.unmarshalReader(r, v.config) } func initConfig(typ, config string, v *Viper) { @@ -622,22 +564,11 @@ func TestJSON(t *testing.T) { assert.Equal(t, "0001", v.Get("id")) } -func TestProperties(t *testing.T) { - v := New() - - v.SetConfigType("properties") - - // Read the properties data into Viper configuration - require.NoError(t, v.ReadConfig(bytes.NewBuffer(propertiesExample)), "Error reading properties data") - - assert.Equal(t, "0001", v.Get("p_id")) -} - func TestTOML(t *testing.T) { v := New() v.SetConfigType("toml") - // Read the properties data into Viper configuration + // Read the TOML data into Viper configuration require.NoError(t, v.ReadConfig(bytes.NewBuffer(tomlExample)), "Error reading toml data") assert.Equal(t, "TOML Example", v.Get("title")) @@ -646,42 +577,16 @@ func TestTOML(t *testing.T) { func TestDotEnv(t *testing.T) { v := New() v.SetConfigType("env") - // Read the properties data into Viper configuration + // Read the dotenv data into Viper configuration require.NoError(t, v.ReadConfig(bytes.NewBuffer(dotenvExample)), "Error reading env data") assert.Equal(t, "DotEnv Example", v.Get("title_dotenv")) } -func TestHCL(t *testing.T) { - v := New() - v.SetConfigType("hcl") - // Read the properties data into Viper configuration - require.NoError(t, v.ReadConfig(bytes.NewBuffer(hclExample)), "Error reading hcl data") - - // initHcl() - assert.Equal(t, "0001", v.Get("id")) - assert.Equal(t, 0.55, v.Get("ppu")) - assert.Equal(t, "donut", v.Get("type")) - assert.Equal(t, "Cake", v.Get("name")) - v.Set("id", "0002") - assert.Equal(t, "0002", v.Get("id")) - assert.NotEqual(t, "cronut", v.Get("type")) -} - -func TestIni(t *testing.T) { - // initIni() - v := New() - v.SetConfigType("ini") - // Read the properties data into Viper configuration - require.NoError(t, v.ReadConfig(bytes.NewBuffer(iniExample)), "Error reading ini data") - - assert.Equal(t, "ini", v.Get("default.name")) -} - func TestRemotePrecedence(t *testing.T) { v := New() v.SetConfigType("json") - // Read the properties data into Viper configuration v.config + // Read the remote data into Viper configuration v.config require.NoError(t, v.ReadConfig(bytes.NewBuffer(jsonExample)), "Error reading json data") assert.Equal(t, "0001", v.Get("id")) @@ -701,7 +606,7 @@ func TestRemotePrecedence(t *testing.T) { func TestEnv(t *testing.T) { v := New() v.SetConfigType("json") - // Read the properties data into Viper configuration v.config + // Read the JSON data into Viper configuration v.config require.NoError(t, v.ReadConfig(bytes.NewBuffer(jsonExample)), "Error reading json data") v.BindEnv("id") @@ -724,7 +629,7 @@ func TestEnv(t *testing.T) { func TestMultipleEnv(t *testing.T) { v := New() v.SetConfigType("json") - // Read the properties data into Viper configuration v.config + // Read the JSON data into Viper configuration v.config require.NoError(t, v.ReadConfig(bytes.NewBuffer(jsonExample)), "Error reading json data") v.BindEnv("f", "FOOD", "OLD_FOOD") @@ -737,7 +642,7 @@ func TestMultipleEnv(t *testing.T) { func TestEmptyEnv(t *testing.T) { v := New() v.SetConfigType("json") - // Read the properties data into Viper configuration v.config + // Read the JSON data into Viper configuration v.config require.NoError(t, v.ReadConfig(bytes.NewBuffer(jsonExample)), "Error reading json data") v.BindEnv("type") // Empty environment variable @@ -752,7 +657,7 @@ func TestEmptyEnv(t *testing.T) { func TestEmptyEnv_Allowed(t *testing.T) { v := New() v.SetConfigType("json") - // Read the properties data into Viper configuration v.config + // Read the JSON data into Viper configuration v.config require.NoError(t, v.ReadConfig(bytes.NewBuffer(jsonExample)), "Error reading json data") v.AllowEmptyEnv(true) @@ -769,7 +674,7 @@ func TestEmptyEnv_Allowed(t *testing.T) { func TestEnvPrefix(t *testing.T) { v := New() v.SetConfigType("json") - // Read the properties data into Viper configuration v.config + // Read the JSON data into Viper configuration v.config require.NoError(t, v.ReadConfig(bytes.NewBuffer(jsonExample)), "Error reading json data") v.SetEnvPrefix("foo") // will be uppercased automatically @@ -829,7 +734,7 @@ func TestEnvKeyReplacer(t *testing.T) { func TestEnvSubConfig(t *testing.T) { v := New() v.SetConfigType("yaml") - // Read the properties data into Viper configuration v.config + // Read the YAML data into Viper configuration v.config require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample)), "Error reading json data") v.AutomaticEnv() v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) @@ -851,10 +756,6 @@ func TestAllKeys(t *testing.T) { ks := []string{ "title", - "author.bio", - "author.e-mail", - "author.github", - "author.name", "newkey", "owner.organization", "owner.dob", @@ -866,21 +767,12 @@ func TestAllKeys(t *testing.T) { "hobbies", "clothing.jacket", "clothing.trousers", - "default.import_path", - "default.name", - "default.version", "clothing.pants.size", "age", "hacker", "id", "type", "eyes", - "p_id", - "p_ppu", - "p_batters.batter.type", - "p_type", - "p_name", - "foos", "title_dotenv", "type_dotenv", "name_dotenv", @@ -893,24 +785,13 @@ func TestAllKeys(t *testing.T) { "dob": dob, }, "title": "TOML Example", - "author": map[string]any{ - "e-mail": "fake@localhost", - "github": "https://github.com/Unknown", - "name": "Unknown", - "bio": "Gopher.\nCoding addict.\nGood man.\n", - }, - "ppu": 0.55, - "eyes": "brown", + "ppu": 0.55, + "eyes": "brown", "clothing": map[string]any{ "trousers": "denim", "jacket": "leather", "pants": map[string]any{"size": "large"}, }, - "default": map[string]any{ - "import_path": "gopkg.in/ini.v1", - "name": "ini", - "version": "v1", - }, "id": "0001", "batters": map[string]any{ "batter": []any{ @@ -927,27 +808,10 @@ func TestAllKeys(t *testing.T) { "snowboarding", "go", }, - "age": 35, - "type": "donut", - "newkey": "remote", - "name": "Cake", - "p_id": "0001", - "p_ppu": "0.55", - "p_name": "Cake", - "p_batters": map[string]any{ - "batter": map[string]any{"type": "Regular"}, - }, - "p_type": "donut", - "foos": []map[string]any{ - { - "foo": []map[string]any{ - {"key": 1}, - {"key": 2}, - {"key": 3}, - {"key": 4}, - }, - }, - }, + "age": 35, + "type": "donut", + "newkey": "remote", + "name": "Cake", "title_dotenv": "DotEnv Example", "type_dotenv": "donut", "name_dotenv": "Cake", @@ -1611,24 +1475,6 @@ func TestFindsNestedKeys(t *testing.T) { "clothing.trousers": "denim", "owner.dob": dob, "beard": true, - "foos": []map[string]any{ - { - "foo": []map[string]any{ - { - "key": 1, - }, - { - "key": 2, - }, - { - "key": 3, - }, - { - "key": 4, - }, - }, - }, - }, } for key, expectedValue := range expected { @@ -1800,32 +1646,6 @@ func TestSub(t *testing.T) { assert.Equal(t, []string{"clothing", "pants"}, subv.parents) } -var hclWriteExpected = []byte(`"foos" = { - "foo" = { - "key" = 1 - } - - "foo" = { - "key" = 2 - } - - "foo" = { - "key" = 3 - } - - "foo" = { - "key" = 4 - } -} - -"id" = "0001" - -"name" = "Cake" - -"ppu" = 0.55 - -"type" = "donut"`) - var jsonWriteExpected = []byte(`{ "batters": { "batter": [ @@ -1849,13 +1669,6 @@ var jsonWriteExpected = []byte(`{ "type": "donut" }`) -var propertiesWriteExpected = []byte(`p_batters.batter.type = Regular -p_id = 0001 -p_name = Cake -p_ppu = 0.55 -p_type = donut -`) - // var yamlWriteExpected = []byte(`age: 35 // beard: true // clothing: @@ -1882,30 +1695,6 @@ func TestWriteConfig(t *testing.T) { input []byte expectedContent []byte }{ - "hcl with file extension": { - configName: "c", - inConfigType: "hcl", - outConfigType: "hcl", - fileName: "c.hcl", - input: hclExample, - expectedContent: hclWriteExpected, - }, - "hcl without file extension": { - configName: "c", - inConfigType: "hcl", - outConfigType: "hcl", - fileName: "c", - input: hclExample, - expectedContent: hclWriteExpected, - }, - "hcl with file extension and mismatch type": { - configName: "c", - inConfigType: "hcl", - outConfigType: "json", - fileName: "c.hcl", - input: hclExample, - expectedContent: hclWriteExpected, - }, "json with file extension": { configName: "c", inConfigType: "json", @@ -1930,22 +1719,6 @@ func TestWriteConfig(t *testing.T) { input: jsonExample, expectedContent: jsonWriteExpected, }, - "properties with file extension": { - configName: "c", - inConfigType: "properties", - outConfigType: "properties", - fileName: "c.properties", - input: propertiesExample, - expectedContent: propertiesWriteExpected, - }, - "properties without file extension": { - configName: "c", - inConfigType: "properties", - outConfigType: "properties", - fileName: "c", - input: propertiesExample, - expectedContent: propertiesWriteExpected, - }, "yaml with file extension": { configName: "c", inConfigType: "yaml", From 42d4cb2065d4b9d1a9fef719c54737d43c4b99f0 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 24 Jun 2024 15:11:28 +0200 Subject: [PATCH 2/4] feat(encoding): use new codec registry Signed-off-by: Mark Sagi-Kazar --- viper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viper.go b/viper.go index 4a0873eca..9fc4fd325 100644 --- a/viper.go +++ b/viper.go @@ -209,7 +209,7 @@ func New() *Viper { v.typeByDefValue = false v.logger = slog.New(&discardHandler{}) - codecRegistry := codecRegistry{v: v} + codecRegistry := NewCodecRegistry() v.encoderRegistry = codecRegistry v.decoderRegistry = codecRegistry From febf23df141c95b2ae617463b95133376ebd7ddd Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 24 Jun 2024 15:11:51 +0200 Subject: [PATCH 3/4] refactor(encoding): drop old codec registry Signed-off-by: Mark Sagi-Kazar --- encoding.go | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/encoding.go b/encoding.go index 1d6cd6037..52511db8b 100644 --- a/encoding.go +++ b/encoding.go @@ -77,46 +77,6 @@ func WithCodecRegistry(r CodecRegistry) Option { }) } -type codecRegistry struct { - v *Viper -} - -func (r codecRegistry) Encoder(format string) (Encoder, error) { - encoder, ok := r.codec(format) - if !ok { - return nil, errors.New("encoder not found for this format") - } - - return encoder, nil -} - -func (r codecRegistry) Decoder(format string) (Decoder, error) { - decoder, ok := r.codec(format) - if !ok { - return nil, errors.New("decoder not found for this format") - } - - return decoder, nil -} - -func (r codecRegistry) codec(format string) (Codec, bool) { - switch strings.ToLower(format) { - case "yaml", "yml": - return yaml.Codec{}, true - - case "json": - return json.Codec{}, true - - case "toml": - return toml.Codec{}, true - - case "dotenv", "env": - return &dotenv.Codec{}, true - } - - return nil, false -} - // DefaultCodecRegistry is a simple implementation of [CodecRegistry] that allows registering custom [Codec]s. type DefaultCodecRegistry struct { codecs map[string]Codec From 488253084d3247463eea157db1e0c6dd3a33c00b Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Mon, 24 Jun 2024 15:18:50 +0200 Subject: [PATCH 4/4] feat(encoding): drop HCL, Java properties and INI dependencies Signed-off-by: Mark Sagi-Kazar --- go.mod | 3 - go.sum | 6 - 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 ---------- viper.go | 11 -- 11 files changed, 699 deletions(-) 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 diff --git a/go.mod b/go.mod index 50ff1a3d8..d205c68a9 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,6 @@ go 1.21 require ( github.com/fsnotify/fsnotify v1.7.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 @@ -14,7 +12,6 @@ require ( 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 ) diff --git a/go.sum b/go.sum index da9f1f046..c8524c854 100644 --- a/go.sum +++ b/go.sum @@ -9,14 +9,10 @@ github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAp github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -56,8 +52,6 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/viper.go b/viper.go index 9fc4fd325..6ec598c48 100644 --- a/viper.go +++ b/viper.go @@ -40,7 +40,6 @@ import ( "github.com/spf13/cast" "github.com/spf13/pflag" - "github.com/spf13/viper/internal/encoding/ini" "github.com/spf13/viper/internal/features" ) @@ -163,9 +162,6 @@ type Viper struct { configPermissions os.FileMode envPrefix string - // Specific commands for ini parsing - iniLoadOptions ini.LoadOptions - automaticEnvApplied bool envKeyReplacer StringReplacer allowEmptyEnv bool @@ -1937,13 +1933,6 @@ func (v *Viper) SetConfigPermissions(perm os.FileMode) { v.configPermissions = perm.Perm() } -// IniLoadOptions sets the load options for ini parsing. -func IniLoadOptions(in ini.LoadOptions) Option { - return optionFunc(func(v *Viper) { - v.iniLoadOptions = in - }) -} - func (v *Viper) getConfigType() string { if v.configType != "" { return v.configType