mirror of
https://github.com/spf13/viper
synced 2025-05-07 20:57:18 +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"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper/internal/encoding/codec"
|
||||||
"github.com/subosito/gotenv"
|
"github.com/subosito/gotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const keyDelimiter = "_"
|
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).
|
// (commonly called as dotenv format).
|
||||||
type Codec struct{}
|
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 := map[string]interface{}{}
|
||||||
|
|
||||||
flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
|
flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
|
||||||
|
@ -40,7 +45,7 @@ func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
return buf.Bytes(), nil
|
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
|
var buf bytes.Buffer
|
||||||
|
|
||||||
_, err := buf.Write(b)
|
_, 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"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/spf13/viper/internal/encoding/codec"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
"github.com/hashicorp/hcl/hcl/printer"
|
"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?
|
// TODO: add printer config to the codec?
|
||||||
type Codec struct{}
|
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)
|
b, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -35,6 +41,6 @@ func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
return buf.Bytes(), nil
|
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)
|
return hcl.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper/internal/encoding/codec"
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"gopkg.in/ini.v1"
|
"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".
|
// This type is added here for convenience: this way consumers can import a single package called "ini".
|
||||||
type LoadOptions = ini.LoadOptions
|
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 {
|
type Codec struct {
|
||||||
KeyDelimiter string
|
KeyDelimiter string
|
||||||
LoadOptions LoadOptions
|
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()
|
cfg := ini.Empty()
|
||||||
ini.PrettyFormat = false
|
ini.PrettyFormat = false
|
||||||
|
|
||||||
|
@ -62,7 +85,7 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
return buf.Bytes(), nil
|
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)
|
cfg := ini.Empty(c.LoadOptions)
|
||||||
|
|
||||||
err := cfg.Append(b)
|
err := cfg.Append(b)
|
||||||
|
@ -90,7 +113,7 @@ func (c Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Codec) keyDelimiter() string {
|
func (c *Codec) keyDelimiter() string {
|
||||||
if c.KeyDelimiter == "" {
|
if c.KeyDelimiter == "" {
|
||||||
return "."
|
return "."
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,13 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper/internal/encoding/codec"
|
||||||
|
|
||||||
"github.com/magiconair/properties"
|
"github.com/magiconair/properties"
|
||||||
"github.com/spf13/cast"
|
"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 {
|
type Codec struct {
|
||||||
KeyDelimiter string
|
KeyDelimiter string
|
||||||
|
|
||||||
|
@ -20,6 +22,20 @@ type Codec struct {
|
||||||
Properties *properties.Properties
|
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) {
|
func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
if c.Properties == nil {
|
if c.Properties == nil {
|
||||||
c.Properties = properties.NewProperties()
|
c.Properties = properties.NewProperties()
|
||||||
|
@ -77,7 +93,7 @@ func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Codec) keyDelimiter() string {
|
func (c *Codec) keyDelimiter() string {
|
||||||
if c.KeyDelimiter == "" {
|
if c.KeyDelimiter == "" {
|
||||||
return "."
|
return "."
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,22 @@ package json
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"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{}
|
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?
|
// TODO: expose prefix and indent in the Codec as setting?
|
||||||
return json.MarshalIndent(v, "", " ")
|
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)
|
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 (
|
import (
|
||||||
"github.com/pelletier/go-toml/v2"
|
"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{}
|
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)
|
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)
|
return toml.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
package yaml
|
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{}
|
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)
|
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)
|
return yaml.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|
119
viper.go
119
viper.go
|
@ -40,13 +40,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/spf13/viper/internal/encoding"
|
"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/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.
|
// ConfigMarshalError happens when failing to marshal the configuration.
|
||||||
|
@ -219,9 +213,7 @@ type Viper struct {
|
||||||
|
|
||||||
logger Logger
|
logger Logger
|
||||||
|
|
||||||
// TODO: should probably be protected with a mutex
|
codecRegistry *encoding.CodecRegistry
|
||||||
encoderRegistry *encoding.EncoderRegistry
|
|
||||||
decoderRegistry *encoding.DecoderRegistry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns an initialized Viper instance.
|
// New returns an initialized Viper instance.
|
||||||
|
@ -242,11 +234,34 @@ func New() *Viper {
|
||||||
v.typeByDefValue = false
|
v.typeByDefValue = false
|
||||||
v.logger = jwwLogger{}
|
v.logger = jwwLogger{}
|
||||||
|
|
||||||
v.resetEncoding()
|
v.resetEncodingWithLazyInitializationMode()
|
||||||
|
|
||||||
return v
|
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.
|
// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney.
|
||||||
// If you're unfamiliar with this style,
|
// If you're unfamiliar with this style,
|
||||||
// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and
|
// 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)
|
opt.apply(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.resetEncoding()
|
v.resetEncodingWithLazyInitializationMode()
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
@ -304,82 +319,8 @@ func Reset() {
|
||||||
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
|
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this lazy initialization instead
|
func (v *Viper) resetEncodingWithLazyInitializationMode() {
|
||||||
func (v *Viper) resetEncoding() {
|
v.codecRegistry = encoding.NewCodecRegistry(v.keyDelim, v.iniLoadOptions)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type defaultRemoteProvider struct {
|
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 {
|
switch format := strings.ToLower(v.getConfigType()); format {
|
||||||
case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "properties", "props", "prop", "dotenv", "env":
|
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 {
|
if err != nil {
|
||||||
return ConfigParseError{err}
|
return ConfigParseError{err}
|
||||||
}
|
}
|
||||||
|
@ -1769,7 +1710,7 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||||
c := v.AllSettings()
|
c := v.AllSettings()
|
||||||
switch configType {
|
switch configType {
|
||||||
case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "prop", "props", "properties", "dotenv", "env":
|
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 {
|
if err != nil {
|
||||||
return ConfigMarshalError{err}
|
return ConfigMarshalError{err}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue