diff --git a/internal/encoding/codec/codec.go b/internal/encoding/codec/codec.go new file mode 100644 index 000000000..6c839e933 --- /dev/null +++ b/internal/encoding/codec/codec.go @@ -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) +} diff --git a/internal/encoding/decoder.go b/internal/encoding/decoder.go deleted file mode 100644 index f472e9ff1..000000000 --- a/internal/encoding/decoder.go +++ /dev/null @@ -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) -} diff --git a/internal/encoding/decoder_test.go b/internal/encoding/decoder_test.go deleted file mode 100644 index 6cb56d021..000000000 --- a/internal/encoding/decoder_test.go +++ /dev/null @@ -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) - } - }) -} diff --git a/internal/encoding/dotenv/codec.go b/internal/encoding/dotenv/codec.go index 4485063b6..2dbea44ad 100644 --- a/internal/encoding/dotenv/codec.go +++ b/internal/encoding/dotenv/codec.go @@ -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) diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go deleted file mode 100644 index 2341bf235..000000000 --- a/internal/encoding/encoder.go +++ /dev/null @@ -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) -} diff --git a/internal/encoding/encoder_test.go b/internal/encoding/encoder_test.go deleted file mode 100644 index adee6d090..000000000 --- a/internal/encoding/encoder_test.go +++ /dev/null @@ -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) - } - }) -} diff --git a/internal/encoding/hcl/codec.go b/internal/encoding/hcl/codec.go index 7fde8e4bc..b22eb33eb 100644 --- a/internal/encoding/hcl/codec.go +++ b/internal/encoding/hcl/codec.go @@ -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) } diff --git a/internal/encoding/ini/codec.go b/internal/encoding/ini/codec.go index 9acd87fc3..4db3cae0f 100644 --- a/internal/encoding/ini/codec.go +++ b/internal/encoding/ini/codec.go @@ -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 "." } diff --git a/internal/encoding/javaproperties/codec.go b/internal/encoding/javaproperties/codec.go index b8a2251c1..0fb3926a0 100644 --- a/internal/encoding/javaproperties/codec.go +++ b/internal/encoding/javaproperties/codec.go @@ -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 "." } diff --git a/internal/encoding/json/codec.go b/internal/encoding/json/codec.go index 1b7caaceb..f404dde51 100644 --- a/internal/encoding/json/codec.go +++ b/internal/encoding/json/codec.go @@ -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) } diff --git a/internal/encoding/registry.go b/internal/encoding/registry.go new file mode 100644 index 000000000..f24366a5b --- /dev/null +++ b/internal/encoding/registry.go @@ -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 +} diff --git a/internal/encoding/registry_test.go b/internal/encoding/registry_test.go new file mode 100644 index 000000000..9ae7972b9 --- /dev/null +++ b/internal/encoding/registry_test.go @@ -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) + } + }) +} diff --git a/internal/encoding/toml/codec.go b/internal/encoding/toml/codec.go index a993c5994..a2db5693f 100644 --- a/internal/encoding/toml/codec.go +++ b/internal/encoding/toml/codec.go @@ -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) } diff --git a/internal/encoding/yaml/codec.go b/internal/encoding/yaml/codec.go index 82dc136a3..933a2fbfb 100644 --- a/internal/encoding/yaml/codec.go +++ b/internal/encoding/yaml/codec.go @@ -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) } diff --git a/viper.go b/viper.go index 0158a7f31..c6b485827 100644 --- a/viper.go +++ b/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} }