mirror of
https://github.com/spf13/viper
synced 2025-05-07 04:37:20 +00:00
feat: lazy initialization codec
This commit is contained in:
parent
097e0d888f
commit
6be49d9472
15 changed files with 371 additions and 383 deletions
11
internal/encoding/codec/codec.go
Normal file
11
internal/encoding/codec/codec.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package codec
|
||||
|
||||
type Codec interface {
|
||||
// Decode decodes the contents of b into v.
|
||||
// It's primarily used for decoding contents of a file into a map[string]interface{}.
|
||||
Decode(b []byte, v map[string]interface{}) error
|
||||
|
||||
// Encode encodes the contents of v into a byte representation.
|
||||
// It's primarily used for encoding a map[string]interface{} into a file format.
|
||||
Encode(v map[string]interface{}) ([]byte, error)
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Decoder decodes the contents of b into v.
|
||||
// It's primarily used for decoding contents of a file into a map[string]interface{}.
|
||||
type Decoder interface {
|
||||
Decode(b []byte, v map[string]interface{}) error
|
||||
}
|
||||
|
||||
const (
|
||||
// ErrDecoderNotFound is returned when there is no decoder registered for a format.
|
||||
ErrDecoderNotFound = encodingError("decoder not found for this format")
|
||||
|
||||
// ErrDecoderFormatAlreadyRegistered is returned when an decoder is already registered for a format.
|
||||
ErrDecoderFormatAlreadyRegistered = encodingError("decoder already registered for this format")
|
||||
)
|
||||
|
||||
// DecoderRegistry can choose an appropriate Decoder based on the provided format.
|
||||
type DecoderRegistry struct {
|
||||
decoders map[string]Decoder
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewDecoderRegistry returns a new, initialized DecoderRegistry.
|
||||
func NewDecoderRegistry() *DecoderRegistry {
|
||||
return &DecoderRegistry{
|
||||
decoders: make(map[string]Decoder),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterDecoder registers a Decoder for a format.
|
||||
// Registering a Decoder for an already existing format is not supported.
|
||||
func (e *DecoderRegistry) RegisterDecoder(format string, enc Decoder) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if _, ok := e.decoders[format]; ok {
|
||||
return ErrDecoderFormatAlreadyRegistered
|
||||
}
|
||||
|
||||
e.decoders[format] = enc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode calls the underlying Decoder based on the format.
|
||||
func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]interface{}) error {
|
||||
e.mu.RLock()
|
||||
decoder, ok := e.decoders[format]
|
||||
e.mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return ErrDecoderNotFound
|
||||
}
|
||||
|
||||
return decoder.Decode(b, v)
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type decoder struct {
|
||||
v map[string]interface{}
|
||||
}
|
||||
|
||||
func (d decoder) Decode(_ []byte, v map[string]interface{}) error {
|
||||
for key, value := range d.v {
|
||||
v[key] = value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDecoderRegistry_RegisterDecoder(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewDecoderRegistry()
|
||||
|
||||
err := registry.RegisterDecoder("myformat", decoder{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AlreadyRegistered", func(t *testing.T) {
|
||||
registry := NewDecoderRegistry()
|
||||
|
||||
err := registry.RegisterDecoder("myformat", decoder{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = registry.RegisterDecoder("myformat", decoder{})
|
||||
if err != ErrDecoderFormatAlreadyRegistered {
|
||||
t.Fatalf("expected ErrDecoderFormatAlreadyRegistered, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecoderRegistry_Decode(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewDecoderRegistry()
|
||||
decoder := decoder{
|
||||
v: map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
|
||||
err := registry.RegisterDecoder("myformat", decoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v := map[string]interface{}{}
|
||||
|
||||
err = registry.Decode("myformat", []byte("key: value"), v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(decoder.v, v) {
|
||||
t.Fatalf("decoded value does not match the expected one\nactual: %+v\nexpected: %+v", v, decoder.v)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecoderNotFound", func(t *testing.T) {
|
||||
registry := NewDecoderRegistry()
|
||||
|
||||
v := map[string]interface{}{}
|
||||
|
||||
err := registry.Decode("myformat", nil, v)
|
||||
if err != ErrDecoderNotFound {
|
||||
t.Fatalf("expected ErrDecoderNotFound, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -6,16 +6,21 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
"github.com/subosito/gotenv"
|
||||
)
|
||||
|
||||
const keyDelimiter = "_"
|
||||
|
||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables
|
||||
// Codec implements the Codec interface for encoding data containing environment variables
|
||||
// (commonly called as dotenv format).
|
||||
type Codec struct{}
|
||||
|
||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
func New(args ...interface{}) codec.Codec {
|
||||
return &Codec{}
|
||||
}
|
||||
|
||||
func (*Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
flattened := map[string]interface{}{}
|
||||
|
||||
flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
|
||||
|
@ -40,7 +45,7 @@ func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
func (*Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
_, err := buf.Write(b)
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Encoder encodes the contents of v into a byte representation.
|
||||
// It's primarily used for encoding a map[string]interface{} into a file format.
|
||||
type Encoder interface {
|
||||
Encode(v map[string]interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
const (
|
||||
// ErrEncoderNotFound is returned when there is no encoder registered for a format.
|
||||
ErrEncoderNotFound = encodingError("encoder not found for this format")
|
||||
|
||||
// ErrEncoderFormatAlreadyRegistered is returned when an encoder is already registered for a format.
|
||||
ErrEncoderFormatAlreadyRegistered = encodingError("encoder already registered for this format")
|
||||
)
|
||||
|
||||
// EncoderRegistry can choose an appropriate Encoder based on the provided format.
|
||||
type EncoderRegistry struct {
|
||||
encoders map[string]Encoder
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewEncoderRegistry returns a new, initialized EncoderRegistry.
|
||||
func NewEncoderRegistry() *EncoderRegistry {
|
||||
return &EncoderRegistry{
|
||||
encoders: make(map[string]Encoder),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterEncoder registers an Encoder for a format.
|
||||
// Registering a Encoder for an already existing format is not supported.
|
||||
func (e *EncoderRegistry) RegisterEncoder(format string, enc Encoder) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if _, ok := e.encoders[format]; ok {
|
||||
return ErrEncoderFormatAlreadyRegistered
|
||||
}
|
||||
|
||||
e.encoders[format] = enc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EncoderRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) {
|
||||
e.mu.RLock()
|
||||
encoder, ok := e.encoders[format]
|
||||
e.mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return nil, ErrEncoderNotFound
|
||||
}
|
||||
|
||||
return encoder.Encode(v)
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
b []byte
|
||||
}
|
||||
|
||||
func (e encoder) Encode(_ map[string]interface{}) ([]byte, error) {
|
||||
return e.b, nil
|
||||
}
|
||||
|
||||
func TestEncoderRegistry_RegisterEncoder(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewEncoderRegistry()
|
||||
|
||||
err := registry.RegisterEncoder("myformat", encoder{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AlreadyRegistered", func(t *testing.T) {
|
||||
registry := NewEncoderRegistry()
|
||||
|
||||
err := registry.RegisterEncoder("myformat", encoder{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = registry.RegisterEncoder("myformat", encoder{})
|
||||
if err != ErrEncoderFormatAlreadyRegistered {
|
||||
t.Fatalf("expected ErrEncoderFormatAlreadyRegistered, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncoderRegistry_Decode(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewEncoderRegistry()
|
||||
encoder := encoder{
|
||||
b: []byte("key: value"),
|
||||
}
|
||||
|
||||
err := registry.RegisterEncoder("myformat", encoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(b) != "key: value" {
|
||||
t.Fatalf("expected 'key: value', got: %#v", string(b))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("EncoderNotFound", func(t *testing.T) {
|
||||
registry := NewEncoderRegistry()
|
||||
|
||||
_, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
||||
if err != ErrEncoderNotFound {
|
||||
t.Fatalf("expected ErrEncoderNotFound, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -4,15 +4,21 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/printer"
|
||||
)
|
||||
|
||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for HCL encoding.
|
||||
// Codec implements the encoding.Codec interface for HCL encoding.
|
||||
// TODO: add printer config to the codec?
|
||||
type Codec struct{}
|
||||
|
||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
func New(args ...interface{}) codec.Codec {
|
||||
return &Codec{}
|
||||
}
|
||||
|
||||
func (*Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -35,6 +41,6 @@ func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
func (*Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
return hcl.Unmarshal(b, &v)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
@ -13,13 +15,34 @@ import (
|
|||
// 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.
|
||||
// Codec implements the encoding.Codec interface for INI encoding.
|
||||
type Codec struct {
|
||||
KeyDelimiter string
|
||||
LoadOptions LoadOptions
|
||||
}
|
||||
|
||||
func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
// New treats its first argument as string for KeyDelimiter and the second as ini.LoadOptions.
|
||||
// The other args will be ignored
|
||||
func New(args ...interface{}) codec.Codec {
|
||||
if len(args) < 2 {
|
||||
return nil
|
||||
}
|
||||
keyDelimiter, ok := args[0].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
loadOptions, ok := args[1].(LoadOptions)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Codec{
|
||||
KeyDelimiter: keyDelimiter,
|
||||
LoadOptions: loadOptions,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
cfg := ini.Empty()
|
||||
ini.PrettyFormat = false
|
||||
|
||||
|
@ -62,7 +85,7 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
cfg := ini.Empty(c.LoadOptions)
|
||||
|
||||
err := cfg.Append(b)
|
||||
|
@ -90,7 +113,7 @@ func (c Codec) Decode(b []byte, v map[string]interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c Codec) keyDelimiter() string {
|
||||
func (c *Codec) keyDelimiter() string {
|
||||
if c.KeyDelimiter == "" {
|
||||
return "."
|
||||
}
|
||||
|
|
|
@ -5,11 +5,13 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
|
||||
"github.com/magiconair/properties"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
|
||||
// Codec implements the encoding.Codec interface for Java properties encoding.
|
||||
type Codec struct {
|
||||
KeyDelimiter string
|
||||
|
||||
|
@ -20,6 +22,20 @@ type Codec struct {
|
|||
Properties *properties.Properties
|
||||
}
|
||||
|
||||
// New treats its first argument as string for KeyDelimiter, the other args will be ignored
|
||||
func New(args ...interface{}) codec.Codec {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
keyDelimiter, ok := args[0].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &Codec{
|
||||
KeyDelimiter: keyDelimiter,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
if c.Properties == nil {
|
||||
c.Properties = properties.NewProperties()
|
||||
|
@ -77,7 +93,7 @@ func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c Codec) keyDelimiter() string {
|
||||
func (c *Codec) keyDelimiter() string {
|
||||
if c.KeyDelimiter == "" {
|
||||
return "."
|
||||
}
|
||||
|
|
|
@ -2,16 +2,22 @@ package json
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
)
|
||||
|
||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
|
||||
// Codec implements the encoding.Codec interface for JSON encoding.
|
||||
type Codec struct{}
|
||||
|
||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
func New(_ ...interface{}) codec.Codec {
|
||||
return &Codec{}
|
||||
}
|
||||
|
||||
func (*Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
// TODO: expose prefix and indent in the Codec as setting?
|
||||
return json.MarshalIndent(v, "", " ")
|
||||
}
|
||||
|
||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
func (*Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
return json.Unmarshal(b, &v)
|
||||
}
|
||||
|
|
115
internal/encoding/registry.go
Normal file
115
internal/encoding/registry.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrCodecNotFound is returned when there is no codec registered for a format.
|
||||
ErrCodecNotFound = encodingError("codec not found for this format")
|
||||
|
||||
// ErrCodecFormatAlreadyRegistered is returned when a codec is already registered for a format.
|
||||
ErrCodecFormatAlreadyRegistered = encodingError("codec already registered for this format")
|
||||
)
|
||||
|
||||
// supportedCodecFormats stores all supported codec, the empty pointers are used to construct a corresponding
|
||||
// codec object without reflection.
|
||||
var supportedCodecFormats = map[string]func(args ...interface{}) codec.Codec{
|
||||
"yaml": yaml.New,
|
||||
"yml": yaml.New,
|
||||
"json": json.New,
|
||||
"toml": toml.New,
|
||||
"hcl": hcl.New,
|
||||
"tfvars": hcl.New,
|
||||
"ini": ini.New,
|
||||
"properties": javaproperties.New,
|
||||
"props": javaproperties.New,
|
||||
"prop": javaproperties.New,
|
||||
"dotenv": dotenv.New,
|
||||
"env": dotenv.New,
|
||||
}
|
||||
|
||||
type CodecRegistry struct {
|
||||
codecs map[string]codec.Codec
|
||||
mu sync.RWMutex
|
||||
|
||||
keyDelim string
|
||||
iniLoadOptions ini.LoadOptions
|
||||
}
|
||||
|
||||
// NewCodecRegistry returns a new, initialized CodecRegistry.
|
||||
func NewCodecRegistry(keyDelim string, iniLoadOptions ini.LoadOptions) *CodecRegistry {
|
||||
return &CodecRegistry{
|
||||
codecs: make(map[string]codec.Codec),
|
||||
keyDelim: keyDelim,
|
||||
iniLoadOptions: iniLoadOptions,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *CodecRegistry) getCodecLazily(format string) (codec.Codec, error) {
|
||||
e.mu.RLock()
|
||||
c, ok := e.codecs[format]
|
||||
e.mu.RUnlock()
|
||||
if ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
newCodecFn, ok := supportedCodecFormats[format]
|
||||
if !ok {
|
||||
return nil, ErrCodecNotFound
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "ini":
|
||||
c = newCodecFn(e.keyDelim, e.iniLoadOptions)
|
||||
case "properties", "props", "prop":
|
||||
c = newCodecFn(e.keyDelim)
|
||||
default:
|
||||
c = newCodecFn()
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.codecs[format] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (e *CodecRegistry) Decode(format string, b []byte, v map[string]interface{}) error {
|
||||
decoder, err := e.getCodecLazily(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return decoder.Decode(b, v)
|
||||
}
|
||||
|
||||
func (e *CodecRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) {
|
||||
decoder, err := e.getCodecLazily(format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decoder.Encode(v)
|
||||
}
|
||||
|
||||
// RegisterCodec registers a Codec for a format.
|
||||
// Registering a Codec for an already existing format is not supported.
|
||||
func (e *CodecRegistry) RegisterCodec(format string, codec codec.Codec) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
if _, ok := e.codecs[format]; ok {
|
||||
return ErrCodecFormatAlreadyRegistered
|
||||
}
|
||||
|
||||
e.codecs[format] = codec
|
||||
|
||||
return nil
|
||||
}
|
125
internal/encoding/registry_test.go
Normal file
125
internal/encoding/registry_test.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper/internal/encoding/constructor"
|
||||
"github.com/spf13/viper/internal/encoding/ini"
|
||||
)
|
||||
|
||||
type codec struct {
|
||||
v map[string]interface{}
|
||||
b []byte
|
||||
}
|
||||
|
||||
func (c *codec) Construct() constructor.Codec {
|
||||
return &codec{}
|
||||
}
|
||||
|
||||
func (c *codec) Encode(_ map[string]interface{}) ([]byte, error) {
|
||||
return c.b, nil
|
||||
}
|
||||
|
||||
func (c *codec) Decode(_ []byte, v map[string]interface{}) error {
|
||||
for key, value := range c.v {
|
||||
v[key] = value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCodecRegistry_RegisterCodec(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewCodecRegistry("", ini.LoadOptions{})
|
||||
|
||||
err := registry.RegisterCodec("myformat", &codec{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AlreadyRegistered", func(t *testing.T) {
|
||||
registry := NewCodecRegistry("", ini.LoadOptions{})
|
||||
|
||||
err := registry.RegisterCodec("myformat", &codec{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = registry.RegisterCodec("myformat", &codec{})
|
||||
if err != ErrCodecFormatAlreadyRegistered {
|
||||
t.Fatalf("expected ErrDecoderFormatAlreadyRegistered, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCodecRegistry_Decode(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewCodecRegistry("", ini.LoadOptions{})
|
||||
decoder := &codec{
|
||||
v: map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
}
|
||||
|
||||
err := registry.RegisterCodec("myformat", decoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v := map[string]interface{}{}
|
||||
|
||||
err = registry.Decode("myformat", []byte("key: value"), v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(decoder.v, v) {
|
||||
t.Fatalf("decoded value does not match the expected one\nactual: %+v\nexpected: %+v", v, decoder.v)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DecoderNotFound", func(t *testing.T) {
|
||||
registry := NewCodecRegistry("", ini.LoadOptions{})
|
||||
|
||||
v := map[string]interface{}{}
|
||||
|
||||
err := registry.Decode("myformat", nil, v)
|
||||
if err != ErrCodecNotFound {
|
||||
t.Fatalf("expected ErrDecoderNotFound, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncoderRegistry_Decode(t *testing.T) {
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
registry := NewCodecRegistry("", ini.LoadOptions{})
|
||||
encoder := &codec{
|
||||
b: []byte("key: value"),
|
||||
}
|
||||
|
||||
err := registry.RegisterCodec("myformat", encoder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(b) != "key: value" {
|
||||
t.Fatalf("expected 'key: value', got: %#v", string(b))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("EncoderNotFound", func(t *testing.T) {
|
||||
registry := NewCodecRegistry("", ini.LoadOptions{})
|
||||
|
||||
_, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
||||
if err != ErrCodecNotFound {
|
||||
t.Fatalf("expected ErrEncoderNotFound, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -2,15 +2,20 @@ package toml
|
|||
|
||||
import (
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
)
|
||||
|
||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
|
||||
// Codec implements the encoding.Codec interface for TOML encoding.
|
||||
type Codec struct{}
|
||||
|
||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
func New(_ ...interface{}) codec.Codec {
|
||||
return &Codec{}
|
||||
}
|
||||
|
||||
func (*Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
return toml.Marshal(v)
|
||||
}
|
||||
|
||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
func (*Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
return toml.Unmarshal(b, &v)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
package yaml
|
||||
|
||||
import "gopkg.in/yaml.v3"
|
||||
import (
|
||||
"github.com/spf13/viper/internal/encoding/codec"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
|
||||
// Codec implements the encoding.Codec interface for YAML encoding.
|
||||
type Codec struct{}
|
||||
|
||||
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
func New(_ ...interface{}) codec.Codec {
|
||||
return &Codec{}
|
||||
}
|
||||
|
||||
func (*Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||
return yaml.Marshal(v)
|
||||
}
|
||||
|
||||
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
func (*Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||
return yaml.Unmarshal(b, &v)
|
||||
}
|
||||
|
|
119
viper.go
119
viper.go
|
@ -40,13 +40,7 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
||||
// ConfigMarshalError happens when failing to marshal the configuration.
|
||||
|
@ -219,9 +213,7 @@ type Viper struct {
|
|||
|
||||
logger Logger
|
||||
|
||||
// TODO: should probably be protected with a mutex
|
||||
encoderRegistry *encoding.EncoderRegistry
|
||||
decoderRegistry *encoding.DecoderRegistry
|
||||
codecRegistry *encoding.CodecRegistry
|
||||
}
|
||||
|
||||
// New returns an initialized Viper instance.
|
||||
|
@ -242,11 +234,34 @@ func New() *Viper {
|
|||
v.typeByDefValue = false
|
||||
v.logger = jwwLogger{}
|
||||
|
||||
v.resetEncoding()
|
||||
v.resetEncodingWithLazyInitializationMode()
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// NewWithLazyMode is the lazy initialization mode for benchmark
|
||||
// todo: remove this API later when creating a PR
|
||||
func NewWithLazyMode() *Viper {
|
||||
v := new(Viper)
|
||||
v.keyDelim = "."
|
||||
v.configName = "config"
|
||||
v.configPermissions = os.FileMode(0o644)
|
||||
v.fs = afero.NewOsFs()
|
||||
v.config = make(map[string]interface{})
|
||||
v.parents = []string{}
|
||||
v.override = make(map[string]interface{})
|
||||
v.defaults = make(map[string]interface{})
|
||||
v.kvstore = make(map[string]interface{})
|
||||
v.pflags = make(map[string]FlagValue)
|
||||
v.env = make(map[string][]string)
|
||||
v.aliases = make(map[string]string)
|
||||
v.typeByDefValue = false
|
||||
v.logger = jwwLogger{}
|
||||
|
||||
v.resetEncodingWithLazyInitializationMode()
|
||||
return v
|
||||
}
|
||||
|
||||
// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney.
|
||||
// If you're unfamiliar with this style,
|
||||
// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and
|
||||
|
@ -290,7 +305,7 @@ func NewWithOptions(opts ...Option) *Viper {
|
|||
opt.apply(v)
|
||||
}
|
||||
|
||||
v.resetEncoding()
|
||||
v.resetEncodingWithLazyInitializationMode()
|
||||
|
||||
return v
|
||||
}
|
||||
|
@ -304,82 +319,8 @@ func Reset() {
|
|||
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
|
||||
}
|
||||
|
||||
// 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
|
||||
func (v *Viper) resetEncodingWithLazyInitializationMode() {
|
||||
v.codecRegistry = encoding.NewCodecRegistry(v.keyDelim, v.iniLoadOptions)
|
||||
}
|
||||
|
||||
type defaultRemoteProvider struct {
|
||||
|
@ -1754,7 +1695,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) 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)
|
||||
err := v.codecRegistry.Decode(format, buf.Bytes(), c)
|
||||
if err != nil {
|
||||
return ConfigParseError{err}
|
||||
}
|
||||
|
@ -1769,7 +1710,7 @@ 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)
|
||||
b, err := v.codecRegistry.Encode(configType, c)
|
||||
if err != nil {
|
||||
return ConfigMarshalError{err}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue