Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change custom unmarshaling order #59

Merged
merged 1 commit into from Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 8 additions & 8 deletions envconfig.go
Expand Up @@ -518,30 +518,30 @@ func processAsDecoder(v string, ef reflect.Value) (bool, error) {
return imp, err
}

if tu, ok := iface.(encoding.BinaryUnmarshaler); ok {
if tu, ok := iface.(encoding.TextUnmarshaler); ok {
imp = true
if err = tu.UnmarshalBinary([]byte(v)); err == nil {
if err = tu.UnmarshalText([]byte(v)); err == nil {
return imp, nil
}
}

if tu, ok := iface.(gob.GobDecoder); ok {
if tu, ok := iface.(json.Unmarshaler); ok {
imp = true
if err = tu.GobDecode([]byte(v)); err == nil {
if err = tu.UnmarshalJSON([]byte(v)); err == nil {
return imp, nil
}
}

if tu, ok := iface.(json.Unmarshaler); ok {
if tu, ok := iface.(encoding.BinaryUnmarshaler); ok {
imp = true
if err = tu.UnmarshalJSON([]byte(v)); err == nil {
if err = tu.UnmarshalBinary([]byte(v)); err == nil {
return imp, nil
}
}

if tu, ok := iface.(encoding.TextUnmarshaler); ok {
if tu, ok := iface.(gob.GobDecoder); ok {
imp = true
if err = tu.UnmarshalText([]byte(v)); err == nil {
if err = tu.GobDecode([]byte(v)); err == nil {
return imp, nil
}
}
Expand Down
175 changes: 162 additions & 13 deletions envconfig_test.go
Expand Up @@ -30,18 +30,74 @@ import (
"github.com/google/go-cmp/cmp"
)

var _ Decoder = (*CustomType)(nil)
var _ Decoder = (*CustomDecoderType)(nil)

// CustomType is used to test custom decode methods.
type CustomType struct {
// CustomDecoderType is used to test custom decoding using Decoder.
type CustomDecoderType struct {
value string
}

func (c *CustomType) EnvDecode(val string) error {
func (c *CustomDecoderType) EnvDecode(val string) error {
c.value = "CUSTOM-" + val
return nil
}

var (
_ encoding.BinaryUnmarshaler = (*CustomStdLibDecodingType)(nil)
_ encoding.TextUnmarshaler = (*CustomStdLibDecodingType)(nil)
_ json.Unmarshaler = (*CustomStdLibDecodingType)(nil)
_ gob.GobDecoder = (*CustomStdLibDecodingType)(nil)
)

// CustomStdLibDecodingType is used to test custom decoding using the standard
// library custom unmarshaling interfaces.
type CustomStdLibDecodingType struct {
// used to control implementations
implementsTextUnmarshaler bool
implementsBinaryUnmarshaler bool
implementsJSONUnmarshaler bool
implementsGobDecoder bool

value string
}

// Equal returns whether the decoded values are equal.
func (c CustomStdLibDecodingType) Equal(c2 CustomStdLibDecodingType) bool {
return c.value == c2.value
}

func (c *CustomStdLibDecodingType) UnmarshalBinary(data []byte) error {
if !c.implementsBinaryUnmarshaler {
return errors.New("binary unmarshaler not implemented")
}
c.value = "BINARY-" + string(data)
return nil
}

func (c *CustomStdLibDecodingType) UnmarshalText(text []byte) error {
if !c.implementsTextUnmarshaler {
return errors.New("text unmarshaler not implemented")
}
c.value = "TEXT-" + string(text)
return nil
}

func (c *CustomStdLibDecodingType) UnmarshalJSON(data []byte) error {
if !c.implementsJSONUnmarshaler {
return errors.New("JSON unmarshaler not implemented")
}
c.value = "JSON-" + string(data)
return nil
}

func (c *CustomStdLibDecodingType) GobDecode(data []byte) error {
if !c.implementsGobDecoder {
return errors.New("Gob decoder not implemented")
}
c.value = "GOB-" + string(data)
return nil
}

var (
_ Decoder = (*CustomTypeError)(nil)
_ encoding.BinaryUnmarshaler = (*CustomTypeError)(nil)
Expand Down Expand Up @@ -1086,22 +1142,112 @@ func TestProcessWith(t *testing.T) {
{
name: "syntax/=key",
input: &struct {
Field CustomType `env:"FIELD=foo"`
Field CustomDecoderType `env:"FIELD=foo"`
}{},
lookuper: MapLookuper(map[string]string{}),
err: ErrInvalidEnvvarName,
},

// Custom decoding from standard library interfaces
{
name: "custom_decoder/gob_decoder",
input: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
implementsGobDecoder: true,
},
},
exp: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
value: "GOB-foo",
},
},
lookuper: MapLookuper(map[string]string{
"FIELD": "foo",
}),
},
{
name: "custom_decoder/binary_unmarshaler",
input: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
implementsBinaryUnmarshaler: true,
implementsGobDecoder: true,
},
},
exp: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
value: "BINARY-foo",
},
},
lookuper: MapLookuper(map[string]string{
"FIELD": "foo",
}),
},
{
name: "custom_decoder/json_unmarshaler",
input: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
implementsBinaryUnmarshaler: true,
implementsJSONUnmarshaler: true,
implementsGobDecoder: true,
},
},
exp: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
implementsTextUnmarshaler: true,
value: "JSON-foo",
},
},
lookuper: MapLookuper(map[string]string{
"FIELD": "foo",
}),
},
{
name: "custom_decoder/text_unmarshaler",
input: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
implementsTextUnmarshaler: true,
implementsBinaryUnmarshaler: true,
implementsJSONUnmarshaler: true,
implementsGobDecoder: true,
},
},
exp: &struct {
Field CustomStdLibDecodingType `env:"FIELD"`
}{
Field: CustomStdLibDecodingType{
implementsTextUnmarshaler: true,
value: "TEXT-foo",
},
},
lookuper: MapLookuper(map[string]string{
"FIELD": "foo",
}),
},

// Custom decoder
{
name: "custom_decoder/struct",
input: &struct {
Field CustomType `env:"FIELD"`
Field CustomDecoderType `env:"FIELD"`
}{},
exp: &struct {
Field CustomType `env:"FIELD"`
Field CustomDecoderType `env:"FIELD"`
}{
Field: CustomType{
Field: CustomDecoderType{
value: "CUSTOM-foo",
},
},
Expand All @@ -1112,12 +1258,12 @@ func TestProcessWith(t *testing.T) {
{
name: "custom_decoder/pointer",
input: &struct {
Field *CustomType `env:"FIELD"`
Field *CustomDecoderType `env:"FIELD"`
}{},
exp: &struct {
Field *CustomType `env:"FIELD"`
Field *CustomDecoderType `env:"FIELD"`
}{
Field: &CustomType{
Field: &CustomDecoderType{
value: "CUSTOM-foo",
},
},
Expand All @@ -1128,7 +1274,7 @@ func TestProcessWith(t *testing.T) {
{
name: "custom_decoder/private",
input: &struct {
field *CustomType `env:"FIELD"`
field *CustomDecoderType `env:"FIELD"`
}{},
lookuper: MapLookuper(map[string]string{}),
err: ErrPrivateField,
Expand Down Expand Up @@ -1837,7 +1983,10 @@ func TestProcessWith(t *testing.T) {

opts := cmp.AllowUnexported(
// Custom decoder type
CustomType{},
CustomDecoderType{},

// Custom standard library interfaces decoder type
CustomStdLibDecodingType{},

// Custom decoder type that returns an error
CustomTypeError{},
Expand Down