mirror of
https://github.com/spf13/viper.git
synced 2026-09-20 19:19:00 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee6cffa48d | ||
|
|
687cfb73cf | ||
|
|
7f90845afc | ||
|
|
6e630e548a | ||
|
|
e033c8e8dc | ||
|
|
8492c8d451 | ||
|
|
a7f64b7f25 | ||
|
|
2a1765daf8 | ||
|
|
d2221e2ef9 | ||
|
|
93bf64aaa3 | ||
|
|
15ad72deb2 | ||
|
|
29a215ca4e | ||
|
|
3a285e0226 |
+169
@@ -0,0 +1,169 @@
|
||||
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) {
|
||||
v.encoderRegistry = r
|
||||
})
|
||||
}
|
||||
|
||||
// WithDecoderRegistry sets a custom [DecoderRegistry].
|
||||
func WithDecoderRegistry(r DecoderRegistry) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
v.decoderRegistry = r
|
||||
})
|
||||
}
|
||||
|
||||
// WithCodecRegistry sets a custom [EncoderRegistry] and [DecoderRegistry].
|
||||
func WithCodecRegistry(r CodecRegistry) Option {
|
||||
return optionFunc(func(v *Viper) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -4,33 +4,24 @@ 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/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/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
|
||||
)
|
||||
|
||||
@@ -5,30 +5,14 @@ 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=
|
||||
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=
|
||||
@@ -68,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=
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
@@ -35,19 +35,11 @@ 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/json"
|
||||
"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,8 @@ type Viper struct {
|
||||
|
||||
logger *slog.Logger
|
||||
|
||||
// TODO: should probably be protected with a mutex
|
||||
encoderRegistry *encoding.EncoderRegistry
|
||||
decoderRegistry *encoding.DecoderRegistry
|
||||
encoderRegistry EncoderRegistry
|
||||
decoderRegistry DecoderRegistry
|
||||
|
||||
experimentalFinder bool
|
||||
experimentalBindStruct bool
|
||||
@@ -217,7 +205,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
|
||||
@@ -268,8 +259,6 @@ func NewWithOptions(opts ...Option) *Viper {
|
||||
opt.apply(v)
|
||||
}
|
||||
|
||||
v.resetEncoding()
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -278,16 +267,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 +282,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"}
|
||||
|
||||
@@ -1715,7 +1619,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 +1639,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 +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
|
||||
|
||||
+15
-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",
|
||||
@@ -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_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 +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",
|
||||
|
||||
Reference in New Issue
Block a user