mirror of
https://github.com/spf13/viper
synced 2024-12-22 19:47:01 +00:00
feat(encoding): expose new encoding layer
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
This commit is contained in:
parent
a11ee9ae99
commit
3a285e0226
3 changed files with 137 additions and 5 deletions
113
encoding.go
Normal file
113
encoding.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncoderRegistry returns an [Encoder] for a given format.
|
||||||
|
// The second return value is false if no [Encoder] is registered for the format.
|
||||||
|
type EncoderRegistry interface {
|
||||||
|
Encoder(format string) (Encoder, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecoderRegistry returns an [Decoder] for a given format.
|
||||||
|
// The second return value is false if no [Decoder] is registered for the format.
|
||||||
|
type DecoderRegistry interface {
|
||||||
|
Decoder(format string) (Decoder, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [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.encoderRegistry2 = r
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDecoderRegistry sets a custom [DecoderRegistry].
|
||||||
|
func WithDecoderRegistry(r DecoderRegistry) Option {
|
||||||
|
return optionFunc(func(v *Viper) {
|
||||||
|
v.decoderRegistry2 = r
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCodecRegistry sets a custom [EncoderRegistry] and [DecoderRegistry].
|
||||||
|
func WithCodecRegistry(r CodecRegistry) Option {
|
||||||
|
return optionFunc(func(v *Viper) {
|
||||||
|
v.encoderRegistry2 = r
|
||||||
|
v.decoderRegistry2 = r
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type codecRegistry struct {
|
||||||
|
v *Viper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r codecRegistry) Encoder(format string) (Encoder, bool) {
|
||||||
|
return r.codec(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r codecRegistry) Decoder(format string) (Decoder, bool) {
|
||||||
|
return r.codec(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r codecRegistry) codec(format string) (Codec, bool) {
|
||||||
|
switch format {
|
||||||
|
case "yaml", "yml":
|
||||||
|
return yaml.Codec{}, true
|
||||||
|
|
||||||
|
case "json":
|
||||||
|
return json.Codec{}, true
|
||||||
|
|
||||||
|
case "toml":
|
||||||
|
return toml.Codec{}, true
|
||||||
|
|
||||||
|
case "hcl", "tfvars":
|
||||||
|
return hcl.Codec{}, true
|
||||||
|
|
||||||
|
case "ini":
|
||||||
|
return ini.Codec{
|
||||||
|
KeyDelimiter: r.v.keyDelim,
|
||||||
|
LoadOptions: r.v.iniLoadOptions,
|
||||||
|
}, true
|
||||||
|
|
||||||
|
case "properties", "props", "prop": // Note: This breaks writing a properties file.
|
||||||
|
return &javaproperties.Codec{
|
||||||
|
KeyDelimiter: v.keyDelim,
|
||||||
|
}, true
|
||||||
|
|
||||||
|
case "dotenv", "env":
|
||||||
|
return &dotenv.Codec{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
23
viper.go
23
viper.go
|
@ -195,6 +195,9 @@ type Viper struct {
|
||||||
encoderRegistry *encoding.EncoderRegistry
|
encoderRegistry *encoding.EncoderRegistry
|
||||||
decoderRegistry *encoding.DecoderRegistry
|
decoderRegistry *encoding.DecoderRegistry
|
||||||
|
|
||||||
|
encoderRegistry2 EncoderRegistry
|
||||||
|
decoderRegistry2 DecoderRegistry
|
||||||
|
|
||||||
experimentalFinder bool
|
experimentalFinder bool
|
||||||
experimentalBindStruct bool
|
experimentalBindStruct bool
|
||||||
}
|
}
|
||||||
|
@ -217,6 +220,11 @@ func New() *Viper {
|
||||||
v.typeByDefValue = false
|
v.typeByDefValue = false
|
||||||
v.logger = slog.New(&discardHandler{})
|
v.logger = slog.New(&discardHandler{})
|
||||||
|
|
||||||
|
codecRegistry := codecRegistry{v: v}
|
||||||
|
|
||||||
|
v.encoderRegistry2 = codecRegistry
|
||||||
|
v.decoderRegistry2 = codecRegistry
|
||||||
|
|
||||||
v.resetEncoding()
|
v.resetEncoding()
|
||||||
|
|
||||||
v.experimentalFinder = features.Finder
|
v.experimentalFinder = features.Finder
|
||||||
|
@ -1715,7 +1723,12 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) 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)
|
decoder, ok := v.decoderRegistry2.Decoder(format)
|
||||||
|
if !ok {
|
||||||
|
return ConfigParseError{errors.New("decoder not found")}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := decoder.Decode(buf.Bytes(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ConfigParseError{err}
|
return ConfigParseError{err}
|
||||||
}
|
}
|
||||||
|
@ -1730,7 +1743,13 @@ 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)
|
encoder, ok := v.encoderRegistry2.Encoder(configType)
|
||||||
|
if !ok {
|
||||||
|
// TODO: return a proper error
|
||||||
|
return ConfigMarshalError{errors.New("encoder not found")}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := encoder.Encode(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ConfigMarshalError{err}
|
return ConfigMarshalError{err}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1849,11 +1849,11 @@ var jsonWriteExpected = []byte(`{
|
||||||
"type": "donut"
|
"type": "donut"
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
var propertiesWriteExpected = []byte(`p_id = 0001
|
var propertiesWriteExpected = []byte(`p_batters.batter.type = Regular
|
||||||
p_type = donut
|
p_id = 0001
|
||||||
p_name = Cake
|
p_name = Cake
|
||||||
p_ppu = 0.55
|
p_ppu = 0.55
|
||||||
p_batters.batter.type = Regular
|
p_type = donut
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// var yamlWriteExpected = []byte(`age: 35
|
// var yamlWriteExpected = []byte(`age: 35
|
||||||
|
|
Loading…
Reference in a new issue