feat(encoding): expose new encoding layer

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
This commit is contained in:
Mark Sagi-Kazar 2024-06-21 14:27:56 +02:00 committed by Márk Sági-Kazár
parent a11ee9ae99
commit 3a285e0226
3 changed files with 137 additions and 5 deletions

113
encoding.go Normal file
View 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
}

View file

@ -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}
} }

View file

@ -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