mirror of
https://github.com/spf13/viper.git
synced 2026-09-20 03:18:21 +00:00
Compare commits
17
Commits
+181
@@ -0,0 +1,181 @@
|
||||
package viper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/dotenv"
|
||||
"github.com/spf13/viper/internal/encoding/json"
|
||||
"github.com/spf13/viper/internal/encoding/toml"
|
||||
"github.com/spf13/viper/internal/encoding/yaml"
|
||||
)
|
||||
|
||||
// Encoder encodes Viper's internal data structures into a byte representation.
|
||||
// It's primarily used for encoding a map[string]any into a file format.
|
||||
type Encoder interface {
|
||||
Encode(v map[string]any) ([]byte, error)
|
||||
}
|
||||
|
||||
// Decoder decodes the contents of a byte slice into Viper's internal data structures.
|
||||
// It's primarily used for decoding contents of a file into a map[string]any.
|
||||
type Decoder interface {
|
||||
Decode(b []byte, v map[string]any) error
|
||||
}
|
||||
|
||||
// Codec combines [Encoder] and [Decoder] interfaces.
|
||||
type Codec interface {
|
||||
Encoder
|
||||
Decoder
|
||||
}
|
||||
|
||||
// TODO: consider adding specific errors for not found scenarios
|
||||
|
||||
// EncoderRegistry returns an [Encoder] for a given format.
|
||||
//
|
||||
// Format is case-insensitive.
|
||||
//
|
||||
// [EncoderRegistry] returns an error if no [Encoder] is registered for the format.
|
||||
type EncoderRegistry interface {
|
||||
Encoder(format string) (Encoder, error)
|
||||
}
|
||||
|
||||
// DecoderRegistry returns an [Decoder] for a given format.
|
||||
//
|
||||
// Format is case-insensitive.
|
||||
//
|
||||
// [DecoderRegistry] returns an error if no [Decoder] is registered for the format.
|
||||
type DecoderRegistry interface {
|
||||
Decoder(format string) (Decoder, error)
|
||||
}
|
||||
|
||||
// [CodecRegistry] combines [EncoderRegistry] and [DecoderRegistry] interfaces.
|
||||
type CodecRegistry interface {
|
||||
EncoderRegistry
|
||||
DecoderRegistry
|
||||
}
|
||||
|
||||
// WithEncoderRegistry sets a custom [EncoderRegistry].
|
||||
func WithEncoderRegistry(r EncoderRegistry) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.encoderRegistry = r
|
||||
})
|
||||
}
|
||||
|
||||
// WithDecoderRegistry sets a custom [DecoderRegistry].
|
||||
func WithDecoderRegistry(r DecoderRegistry) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.decoderRegistry = r
|
||||
})
|
||||
}
|
||||
|
||||
// WithCodecRegistry sets a custom [EncoderRegistry] and [DecoderRegistry].
|
||||
func WithCodecRegistry(r CodecRegistry) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.encoderRegistry = r
|
||||
v.decoderRegistry = r
|
||||
})
|
||||
}
|
||||
|
||||
// DefaultCodecRegistry is a simple implementation of [CodecRegistry] that allows registering custom [Codec]s.
|
||||
type DefaultCodecRegistry struct {
|
||||
codecs map[string]Codec
|
||||
|
||||
mu sync.RWMutex
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewCodecRegistry returns a new [CodecRegistry], ready to accept custom [Codec]s.
|
||||
func NewCodecRegistry() *DefaultCodecRegistry {
|
||||
r := &DefaultCodecRegistry{}
|
||||
|
||||
r.init()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *DefaultCodecRegistry) init() {
|
||||
r.once.Do(func() {
|
||||
r.codecs = map[string]Codec{}
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterCodec registers a custom [Codec].
|
||||
//
|
||||
// Format is case-insensitive.
|
||||
func (r *DefaultCodecRegistry) RegisterCodec(format string, codec Codec) error {
|
||||
r.init()
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.codecs[strings.ToLower(format)] = codec
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encoder implements the [EncoderRegistry] interface.
|
||||
//
|
||||
// Format is case-insensitive.
|
||||
func (r *DefaultCodecRegistry) 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
|
||||
}
|
||||
|
||||
// Decoder implements the [DecoderRegistry] interface.
|
||||
//
|
||||
// Format is case-insensitive.
|
||||
func (r *DefaultCodecRegistry) 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 *DefaultCodecRegistry) codec(format string) (Codec, bool) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
format = strings.ToLower(format)
|
||||
|
||||
if r.codecs != nil {
|
||||
codec, ok := r.codecs[format]
|
||||
if ok {
|
||||
return codec, true
|
||||
}
|
||||
}
|
||||
|
||||
switch 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
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package viper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type codec struct{}
|
||||
|
||||
func (codec) Encode(_ map[string]any) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (codec) Decode(_ []byte, _ map[string]any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDefaultCodecRegistry(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewCodecRegistry()
|
||||
|
||||
c := codec{}
|
||||
|
||||
err := registry.RegisterCodec("myformat", c)
|
||||
require.NoError(t, err)
|
||||
|
||||
encoder, err := registry.Encoder("myformat")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, encoder)
|
||||
|
||||
decoder, err := registry.Decoder("myformat")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, decoder)
|
||||
})
|
||||
|
||||
t.Run("CodecNotFound", func(t *testing.T) {
|
||||
registry := NewCodecRegistry()
|
||||
|
||||
_, err := registry.Encoder("myformat")
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = registry.Decoder("myformat")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("FormatIsCaseInsensitive", func(t *testing.T) {
|
||||
registry := NewCodecRegistry()
|
||||
|
||||
c := codec{}
|
||||
|
||||
err := registry.RegisterCodec("MYFORMAT", c)
|
||||
require.NoError(t, err)
|
||||
|
||||
{
|
||||
encoder, err := registry.Encoder("myformat")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, encoder)
|
||||
}
|
||||
|
||||
{
|
||||
encoder, err := registry.Encoder("MYFORMAT")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, encoder)
|
||||
}
|
||||
|
||||
{
|
||||
decoder, err := registry.Decoder("myformat")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, decoder)
|
||||
}
|
||||
|
||||
{
|
||||
decoder, err := registry.Decoder("MYFORMAT")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, decoder)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OverrideDefault", func(t *testing.T) {
|
||||
registry := NewCodecRegistry()
|
||||
|
||||
c := codec{}
|
||||
|
||||
err := registry.RegisterCodec("yaml", c)
|
||||
require.NoError(t, err)
|
||||
|
||||
encoder, err := registry.Encoder("yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, encoder)
|
||||
|
||||
decoder, err := registry.Decoder("yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, c, decoder)
|
||||
})
|
||||
}
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
// WithFinder sets a custom [Finder].
|
||||
func WithFinder(f Finder) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.finder = f
|
||||
})
|
||||
}
|
||||
@@ -34,6 +38,10 @@ func (c *combinedFinder) Find(fsys afero.Fs) ([]string, error) {
|
||||
var errs []error
|
||||
|
||||
for _, finder := range c.finders {
|
||||
if finder == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
r, err := finder.Find(fsys)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+1
-4
@@ -6,7 +6,7 @@ replace github.com/spf13/viper => ../
|
||||
|
||||
require (
|
||||
github.com/sagikazarmark/crypt v0.21.0
|
||||
github.com/spf13/viper v1.20.0-alpha.2
|
||||
github.com/spf13/viper v1.20.0-alpha.3
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -39,11 +39,9 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
@@ -88,6 +86,5 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -150,8 +150,6 @@ github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
|
||||
@@ -176,8 +174,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -449,8 +445,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -40,14 +40,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -170,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
|
||||
@@ -191,9 +180,10 @@ type Viper struct {
|
||||
|
||||
logger *slog.Logger
|
||||
|
||||
// TODO: should probably be protected with a mutex
|
||||
encoderRegistry *encoding.EncoderRegistry
|
||||
decoderRegistry *encoding.DecoderRegistry
|
||||
encoderRegistry EncoderRegistry
|
||||
decoderRegistry DecoderRegistry
|
||||
|
||||
decodeHook mapstructure.DecodeHookFunc
|
||||
|
||||
experimentalFinder bool
|
||||
experimentalBindStruct bool
|
||||
@@ -217,7 +207,10 @@ func New() *Viper {
|
||||
v.typeByDefValue = false
|
||||
v.logger = slog.New(&discardHandler{})
|
||||
|
||||
v.resetEncoding()
|
||||
codecRegistry := NewCodecRegistry()
|
||||
|
||||
v.encoderRegistry = codecRegistry
|
||||
v.decoderRegistry = codecRegistry
|
||||
|
||||
v.experimentalFinder = features.Finder
|
||||
v.experimentalBindStruct = features.BindStruct
|
||||
@@ -256,10 +249,25 @@ type StringReplacer interface {
|
||||
// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys.
|
||||
func EnvKeyReplacer(r StringReplacer) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.envKeyReplacer = r
|
||||
})
|
||||
}
|
||||
|
||||
// WithDecodeHook sets a default decode hook for mapstructure.
|
||||
func WithDecodeHook(h mapstructure.DecodeHookFunc) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v.decodeHook = h
|
||||
})
|
||||
}
|
||||
|
||||
// NewWithOptions creates a new Viper instance.
|
||||
func NewWithOptions(opts ...Option) *Viper {
|
||||
v := New()
|
||||
@@ -268,8 +276,6 @@ func NewWithOptions(opts ...Option) *Viper {
|
||||
opt.apply(v)
|
||||
}
|
||||
|
||||
v.resetEncoding()
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -278,16 +284,9 @@ func NewWithOptions(opts ...Option) *Viper {
|
||||
// Be careful when using this function: subsequent calls may override options you set.
|
||||
// It's always better to use a local Viper instance.
|
||||
func SetOptions(opts ...Option) {
|
||||
keyDelim := v.keyDelim
|
||||
|
||||
for _, opt := range opts {
|
||||
opt.apply(v)
|
||||
}
|
||||
|
||||
// reset encoding if key delimiter changed
|
||||
if keyDelim != v.keyDelim {
|
||||
v.resetEncoding()
|
||||
}
|
||||
}
|
||||
|
||||
// Reset is intended for testing, will reset all to default settings.
|
||||
@@ -300,84 +299,6 @@ func Reset() {
|
||||
resetRemote()
|
||||
}
|
||||
|
||||
// TODO: make this lazy initialization instead.
|
||||
func (v *Viper) resetEncoding() {
|
||||
encoderRegistry := encoding.NewEncoderRegistry()
|
||||
decoderRegistry := encoding.NewDecoderRegistry()
|
||||
|
||||
{
|
||||
codec := yaml.Codec{}
|
||||
|
||||
encoderRegistry.RegisterEncoder("yaml", codec)
|
||||
decoderRegistry.RegisterDecoder("yaml", codec)
|
||||
|
||||
encoderRegistry.RegisterEncoder("yml", codec)
|
||||
decoderRegistry.RegisterDecoder("yml", codec)
|
||||
}
|
||||
|
||||
{
|
||||
codec := json.Codec{}
|
||||
|
||||
encoderRegistry.RegisterEncoder("json", codec)
|
||||
decoderRegistry.RegisterDecoder("json", codec)
|
||||
}
|
||||
|
||||
{
|
||||
codec := toml.Codec{}
|
||||
|
||||
encoderRegistry.RegisterEncoder("toml", codec)
|
||||
decoderRegistry.RegisterDecoder("toml", codec)
|
||||
}
|
||||
|
||||
{
|
||||
codec := hcl.Codec{}
|
||||
|
||||
encoderRegistry.RegisterEncoder("hcl", codec)
|
||||
decoderRegistry.RegisterDecoder("hcl", codec)
|
||||
|
||||
encoderRegistry.RegisterEncoder("tfvars", codec)
|
||||
decoderRegistry.RegisterDecoder("tfvars", codec)
|
||||
}
|
||||
|
||||
{
|
||||
codec := ini.Codec{
|
||||
KeyDelimiter: v.keyDelim,
|
||||
LoadOptions: v.iniLoadOptions,
|
||||
}
|
||||
|
||||
encoderRegistry.RegisterEncoder("ini", codec)
|
||||
decoderRegistry.RegisterDecoder("ini", codec)
|
||||
}
|
||||
|
||||
{
|
||||
codec := &javaproperties.Codec{
|
||||
KeyDelimiter: v.keyDelim,
|
||||
}
|
||||
|
||||
encoderRegistry.RegisterEncoder("properties", codec)
|
||||
decoderRegistry.RegisterDecoder("properties", codec)
|
||||
|
||||
encoderRegistry.RegisterEncoder("props", codec)
|
||||
decoderRegistry.RegisterDecoder("props", codec)
|
||||
|
||||
encoderRegistry.RegisterEncoder("prop", codec)
|
||||
decoderRegistry.RegisterDecoder("prop", codec)
|
||||
}
|
||||
|
||||
{
|
||||
codec := &dotenv.Codec{}
|
||||
|
||||
encoderRegistry.RegisterEncoder("dotenv", codec)
|
||||
decoderRegistry.RegisterDecoder("dotenv", codec)
|
||||
|
||||
encoderRegistry.RegisterEncoder("env", codec)
|
||||
decoderRegistry.RegisterDecoder("env", codec)
|
||||
}
|
||||
|
||||
v.encoderRegistry = encoderRegistry
|
||||
v.decoderRegistry = decoderRegistry
|
||||
}
|
||||
|
||||
// SupportedExts are universally supported extensions.
|
||||
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
|
||||
|
||||
@@ -992,7 +913,7 @@ func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
|
||||
}
|
||||
|
||||
func (v *Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
|
||||
return decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
|
||||
return decode(v.Get(key), v.defaultDecoderConfig(rawVal, opts...))
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
||||
@@ -1015,13 +936,13 @@ func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
|
||||
}
|
||||
|
||||
// TODO: struct keys should be enough?
|
||||
return decode(v.getSettings(keys), defaultDecoderConfig(rawVal, opts...))
|
||||
return decode(v.getSettings(keys), v.defaultDecoderConfig(rawVal, opts...))
|
||||
}
|
||||
|
||||
func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]string, error) {
|
||||
var structKeyMap map[string]any
|
||||
|
||||
err := decode(input, defaultDecoderConfig(&structKeyMap, opts...))
|
||||
err := decode(input, v.defaultDecoderConfig(&structKeyMap, opts...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1038,20 +959,29 @@ func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]stri
|
||||
|
||||
// defaultDecoderConfig returns default mapstructure.DecoderConfig with support
|
||||
// of time.Duration values & string slices.
|
||||
func defaultDecoderConfig(output any, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
||||
c := &mapstructure.DecoderConfig{
|
||||
Metadata: nil,
|
||||
Result: output,
|
||||
WeaklyTypedInput: true,
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
func (v *Viper) defaultDecoderConfig(output any, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
||||
decodeHook := v.decodeHook
|
||||
if decodeHook == nil {
|
||||
decodeHook = mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
// mapstructure.StringToSliceHookFunc(","),
|
||||
stringToWeakSliceHookFunc(","),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
c := &mapstructure.DecoderConfig{
|
||||
Metadata: nil,
|
||||
WeaklyTypedInput: true,
|
||||
DecodeHook: decodeHook,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
// Do not allow overwriting the output
|
||||
c.Result = output
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -1093,7 +1023,7 @@ func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
|
||||
}
|
||||
|
||||
func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
|
||||
config := defaultDecoderConfig(rawVal, opts...)
|
||||
config := v.defaultDecoderConfig(rawVal, opts...)
|
||||
config.ErrorUnused = true
|
||||
|
||||
keys := v.AllKeys()
|
||||
@@ -1715,7 +1645,12 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error {
|
||||
|
||||
switch format := strings.ToLower(v.getConfigType()); format {
|
||||
case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "properties", "props", "prop", "dotenv", "env":
|
||||
err := v.decoderRegistry.Decode(format, buf.Bytes(), c)
|
||||
decoder, err := v.decoderRegistry.Decoder(format)
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
||||
err = decoder.Decode(buf.Bytes(), c)
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
@@ -1730,7 +1665,12 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||
c := v.AllSettings()
|
||||
switch configType {
|
||||
case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "prop", "props", "properties", "dotenv", "env":
|
||||
b, err := v.encoderRegistry.Encode(configType, c)
|
||||
encoder, err := v.encoderRegistry.Encoder(configType)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
||||
b, err := encoder.Encode(c)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
@@ -2019,13 +1959,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
|
||||
|
||||
+50
-242
@@ -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",
|
||||
@@ -1030,6 +894,41 @@ func TestUnmarshal(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithDefaultDecodeHook(t *testing.T) {
|
||||
opt := mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
// Custom Decode Hook Function
|
||||
func(rf reflect.Kind, rt reflect.Kind, data any) (any, error) {
|
||||
if rf != reflect.String || rt != reflect.Map {
|
||||
return data, nil
|
||||
}
|
||||
m := map[string]string{}
|
||||
raw := data.(string)
|
||||
if raw == "" {
|
||||
return m, nil
|
||||
}
|
||||
err := json.Unmarshal([]byte(raw), &m)
|
||||
return m, err
|
||||
},
|
||||
)
|
||||
|
||||
v := NewWithOptions(WithDecodeHook(opt))
|
||||
v.Set("credentials", "{\"foo\":\"bar\"}")
|
||||
|
||||
type config struct {
|
||||
Credentials map[string]string
|
||||
}
|
||||
|
||||
var C config
|
||||
|
||||
require.NoError(t, v.Unmarshal(&C), "unable to decode into struct")
|
||||
|
||||
assert.Equal(t, &config{
|
||||
Credentials: map[string]string{"foo": "bar"},
|
||||
}, &C)
|
||||
}
|
||||
|
||||
func TestUnmarshalWithDecoderOptions(t *testing.T) {
|
||||
v := New()
|
||||
v.Set("credentials", "{\"foo\":\"bar\"}")
|
||||
@@ -1611,24 +1510,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 +1681,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 +1704,6 @@ var jsonWriteExpected = []byte(`{
|
||||
"type": "donut"
|
||||
}`)
|
||||
|
||||
var propertiesWriteExpected = []byte(`p_id = 0001
|
||||
p_type = donut
|
||||
p_name = Cake
|
||||
p_ppu = 0.55
|
||||
p_batters.batter.type = Regular
|
||||
`)
|
||||
|
||||
// var yamlWriteExpected = []byte(`age: 35
|
||||
// beard: true
|
||||
// clothing:
|
||||
@@ -1882,30 +1730,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 +1754,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",
|
||||
|
||||
Reference in New Issue
Block a user