mirror of https://github.com/spf13/viper.git
210 lines
4.6 KiB
Go
210 lines
4.6 KiB
Go
package viper
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/spf13/viper/internal/encoding/dotenv"
|
|
"github.com/spf13/viper/internal/encoding/json"
|
|
"github.com/spf13/viper/internal/encoding/toml"
|
|
"github.com/spf13/viper/internal/encoding/yaml"
|
|
)
|
|
|
|
// Encoder encodes Viper's internal data structures into a byte representation.
|
|
// It's primarily used for encoding a map[string]any into a file format.
|
|
type Encoder interface {
|
|
Encode(v map[string]any) ([]byte, error)
|
|
}
|
|
|
|
// Decoder decodes the contents of a byte slice into Viper's internal data structures.
|
|
// It's primarily used for decoding contents of a file into a map[string]any.
|
|
type Decoder interface {
|
|
Decode(b []byte, v map[string]any) error
|
|
}
|
|
|
|
// Codec combines [Encoder] and [Decoder] interfaces.
|
|
type Codec interface {
|
|
Encoder
|
|
Decoder
|
|
}
|
|
|
|
// TODO: consider adding specific errors for not found scenarios
|
|
|
|
// EncoderRegistry returns an [Encoder] for a given format.
|
|
//
|
|
// Format is case-insensitive.
|
|
//
|
|
// [EncoderRegistry] returns an error if no [Encoder] is registered for the format.
|
|
type EncoderRegistry interface {
|
|
Encoder(format string) (Encoder, error)
|
|
}
|
|
|
|
// DecoderRegistry returns an [Decoder] for a given format.
|
|
//
|
|
// Format is case-insensitive.
|
|
//
|
|
// [DecoderRegistry] returns an error if no [Decoder] is registered for the format.
|
|
type DecoderRegistry interface {
|
|
Decoder(format string) (Decoder, error)
|
|
}
|
|
|
|
// [CodecRegistry] combines [EncoderRegistry] and [DecoderRegistry] interfaces.
|
|
type CodecRegistry interface {
|
|
EncoderRegistry
|
|
DecoderRegistry
|
|
}
|
|
|
|
// WithEncoderRegistry sets a custom [EncoderRegistry].
|
|
func WithEncoderRegistry(r EncoderRegistry) Option {
|
|
return optionFunc(func(v *Viper) {
|
|
v.encoderRegistry = r
|
|
})
|
|
}
|
|
|
|
// WithDecoderRegistry sets a custom [DecoderRegistry].
|
|
func WithDecoderRegistry(r DecoderRegistry) Option {
|
|
return optionFunc(func(v *Viper) {
|
|
v.decoderRegistry = r
|
|
})
|
|
}
|
|
|
|
// WithCodecRegistry sets a custom [EncoderRegistry] and [DecoderRegistry].
|
|
func WithCodecRegistry(r CodecRegistry) Option {
|
|
return optionFunc(func(v *Viper) {
|
|
v.encoderRegistry = r
|
|
v.decoderRegistry = r
|
|
})
|
|
}
|
|
|
|
type codecRegistry struct {
|
|
v *Viper
|
|
}
|
|
|
|
func (r codecRegistry) Encoder(format string) (Encoder, error) {
|
|
encoder, ok := r.codec(format)
|
|
if !ok {
|
|
return nil, errors.New("encoder not found for this format")
|
|
}
|
|
|
|
return encoder, nil
|
|
}
|
|
|
|
func (r codecRegistry) Decoder(format string) (Decoder, error) {
|
|
decoder, ok := r.codec(format)
|
|
if !ok {
|
|
return nil, errors.New("decoder not found for this format")
|
|
}
|
|
|
|
return decoder, nil
|
|
}
|
|
|
|
func (r codecRegistry) codec(format string) (Codec, bool) {
|
|
switch strings.ToLower(format) {
|
|
case "yaml", "yml":
|
|
return yaml.Codec{}, true
|
|
|
|
case "json":
|
|
return json.Codec{}, true
|
|
|
|
case "toml":
|
|
return toml.Codec{}, true
|
|
|
|
case "dotenv", "env":
|
|
return &dotenv.Codec{}, true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// DefaultCodecRegistry is a simple implementation of [CodecRegistry] that allows registering custom [Codec]s.
|
|
type DefaultCodecRegistry struct {
|
|
codecs map[string]Codec
|
|
|
|
mu sync.RWMutex
|
|
once sync.Once
|
|
}
|
|
|
|
// NewCodecRegistry returns a new [CodecRegistry], ready to accept custom [Codec]s.
|
|
func NewCodecRegistry() *DefaultCodecRegistry {
|
|
r := &DefaultCodecRegistry{}
|
|
|
|
r.init()
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *DefaultCodecRegistry) init() {
|
|
r.once.Do(func() {
|
|
r.codecs = map[string]Codec{}
|
|
})
|
|
}
|
|
|
|
// RegisterCodec registers a custom [Codec].
|
|
//
|
|
// Format is case-insensitive.
|
|
func (r *DefaultCodecRegistry) RegisterCodec(format string, codec Codec) error {
|
|
r.init()
|
|
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
r.codecs[strings.ToLower(format)] = codec
|
|
|
|
return nil
|
|
}
|
|
|
|
// Encoder implements the [EncoderRegistry] interface.
|
|
//
|
|
// Format is case-insensitive.
|
|
func (r *DefaultCodecRegistry) Encoder(format string) (Encoder, error) {
|
|
encoder, ok := r.codec(format)
|
|
if !ok {
|
|
return nil, errors.New("encoder not found for this format")
|
|
}
|
|
|
|
return encoder, nil
|
|
}
|
|
|
|
// Decoder implements the [DecoderRegistry] interface.
|
|
//
|
|
// Format is case-insensitive.
|
|
func (r *DefaultCodecRegistry) Decoder(format string) (Decoder, error) {
|
|
decoder, ok := r.codec(format)
|
|
if !ok {
|
|
return nil, errors.New("decoder not found for this format")
|
|
}
|
|
|
|
return decoder, nil
|
|
}
|
|
|
|
func (r *DefaultCodecRegistry) codec(format string) (Codec, bool) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
format = strings.ToLower(format)
|
|
|
|
if r.codecs != nil {
|
|
codec, ok := r.codecs[format]
|
|
if ok {
|
|
return codec, true
|
|
}
|
|
}
|
|
|
|
switch format {
|
|
case "yaml", "yml":
|
|
return yaml.Codec{}, true
|
|
|
|
case "json":
|
|
return json.Codec{}, true
|
|
|
|
case "toml":
|
|
return toml.Codec{}, true
|
|
|
|
case "dotenv", "env":
|
|
return &dotenv.Codec{}, true
|
|
}
|
|
|
|
return nil, false
|
|
}
|