Skip to content

Commit

Permalink
Merge pull request #33 from antonmashko/24-custom-unmarshalling
Browse files Browse the repository at this point in the history
#24 encoding.TextUnmarshaller & encoding.BinaryUnmarshaller support
  • Loading branch information
antonmashko authored Sep 23, 2023
2 parents 65d9813 + f320366 commit 36d2b12
Show file tree
Hide file tree
Showing 12 changed files with 358 additions and 140 deletions.
21 changes: 0 additions & 21 deletions config_sources.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package envconf

import (
"errors"
"flag"
"os"
"reflect"
Expand All @@ -22,14 +21,6 @@ const (
valDefault = "*"
)

var (
//errors
errInvalidFiled = errors.New("invalid field")
errFiledIsNotSettable = errors.New("field is not settable")
ErrUnsupportedType = errors.New("unsupported type")
errConfigurationNotSpecified = errors.New("configuration not specified")
)

type flagSource struct {
name string
v string
Expand Down Expand Up @@ -116,14 +107,6 @@ func newExternalValueSource(f field, ext *externalConfig) *externalValueSource {
}
}

func (s *externalValueSource) Name() string {
name, ok := s.f.structField().Tag.Lookup(tagEnv)
if !ok {
name = s.f.name()
}
return name
}

func (s *externalValueSource) Value() (interface{}, bool) {
return s.ext.get(s.f)
}
Expand All @@ -139,10 +122,6 @@ func newDefaultValueSource(tag reflect.StructField) *defaultValueSource {
return &s
}

func (s *defaultValueSource) Name() string {
return tagDefault
}

func (s *defaultValueSource) Value() (interface{}, bool) {
return s.v, s.defined
}
12 changes: 11 additions & 1 deletion error.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
package envconf

import "fmt"
import (
"errors"
"fmt"
)

var (
// ErrNilData mean that exists nil pointer inside data struct
ErrNilData = errors.New("nil data")
ErrUnsupportedType = errors.New("unsupported type")
ErrConfigurationNotFound = errors.New("configuration not found")
)

type Error struct {
Inner error
Expand Down
54 changes: 53 additions & 1 deletion external/external_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,67 @@
package external

import (
"errors"
"io"
"os"
"testing"

"github.com/antonmashko/envconf/option"
)

func TestWithFlagConfigFile_Ok(t *testing.T) {
opt := WithFlagConfigFile("config", "", "", func(b []byte) error {
f, err := os.CreateTemp("", "envconf.tmp")
if err != nil {
t.Fatal("os.Create:", err)
}
defer f.Close()
defer os.Remove(f.Name())
const content = `{"foo":"bar"}`
_, err = io.WriteString(f, content)
if err != nil {
t.Fatal("io.WriteString: ", err)
}
var result string
opt := WithFlagConfigFile("config1", f.Name(), "", func(b []byte) error {
result = string(b)
return nil
})
opts := &option.Options{}
opt.Apply(opts)
if err = opts.FlagParsed()(); err != nil {
t.Fatal("opts.FlagParsed(): ", err)
}
if result != content {
t.Fatal("unexpected result: ", result)
}
}

func TestWithFlagConfigFile_NotExist_Err(t *testing.T) {
opt := WithFlagConfigFile("config2", "./conf.json", "", func(b []byte) error {
return nil
})
opts := &option.Options{}
opt.Apply(opts)
if err := opts.FlagParsed()(); err == nil {
t.Fatal("expected error but got nil")
}
}

func TestWithFlagConfigFile_CustomError_Err(t *testing.T) {
f, err := os.CreateTemp("", "envconf.tmp")
if err != nil {
t.Fatal("os.Create:", err)
}
defer f.Close()
defer os.Remove(f.Name())

cErr := errors.New("custom error")
opt := WithFlagConfigFile("config3", f.Name(), "", func(b []byte) error {
return cErr
})
opts := &option.Options{}
opt.Apply(opts)
if err = opts.FlagParsed()(); err == nil || err != cErr {
t.Fatal("opts.FlagParsed() unexpected error: ", err)
}
}
51 changes: 43 additions & 8 deletions field.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package envconf

import (
"net/url"
"encoding"
"reflect"
"time"
)

const fieldNameDelim = "."
Expand Down Expand Up @@ -48,14 +47,18 @@ func (emptyField) structField() reflect.StructField {
}

func createFieldFromValue(v reflect.Value, p *structType, t reflect.StructField) field {
// validate reflect value
if !v.CanInterface() {
return emptyField{}
}
switch v.Kind() {
case reflect.Struct:
switch v.Interface().(type) {
case url.URL, time.Time:
return newPrimitiveType(v, p, t)
default:
return newStructType(v, p, t)
// implementations check
implF := asImpl(v)
if implF != nil {
return newFieldType(v, p, t)
}
return newStructType(v, p, t)
case reflect.Ptr:
return newPtrType(v, p, t)
case reflect.Interface:
Expand All @@ -64,7 +67,7 @@ func createFieldFromValue(v reflect.Value, p *structType, t reflect.StructField)
// unsupported types
return emptyField{}
default:
return newPrimitiveType(v, p, t)
return newFieldType(v, p, t)
}
}

Expand All @@ -82,3 +85,35 @@ func fullname(f field) string {
}
return name
}

func asImpl(field reflect.Value) func([]byte) error {
f := func(v interface{}) func([]byte) error {
// encoding.TextUnmarshaler
tu, ok := v.(encoding.TextUnmarshaler)
if ok {
return tu.UnmarshalText
}
// encoding.BinaryUnmarshaller
bu, ok := v.(encoding.BinaryUnmarshaler)
if ok {
return bu.UnmarshalBinary
}
// ----
return nil
}
// NOTE: max double pointer support
for i := 0; i < 3; i++ {
resF := f(field.Interface())
if resF != nil {
return resF
}
if !field.CanAddr() {
return nil
}
field = field.Addr()
if !field.CanInterface() {
return nil
}
}
return nil
}
27 changes: 27 additions & 0 deletions field_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package envconf

import (
"testing"
)

func TestEmptyField_Ok(t *testing.T) {
et := emptyField{}
if err := et.init(); err != nil {
t.Fatal("emptyField.init: ", err)
}
if err := et.define(); err != nil {
t.Fatal("emptyField.define: ", err)
}
if et.isSet() {
t.Fatal("emptyField.isSet: true")
}
if et.name() != "" {
t.Fatal("emptyField.name: ", et.name())
}
if et.parent() != nil {
t.Fatal("emptyField.parent: ", et.parent())
}
if et.structField().Tag != "" {
t.Fatal("emptyField.structField: ", et.structField().Tag)
}
}
10 changes: 3 additions & 7 deletions parser.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package envconf

import (
"errors"
"flag"

"github.com/antonmashko/envconf/option"
)

// ErrNilData mean that exists nil pointer inside data struct
var ErrNilData = errors.New("nil data")

type EnvConf struct {
external *externalConfig
opts *option.Options
Expand All @@ -27,7 +23,7 @@ func NewWithExternal(e External) *EnvConf {
}

func (e *EnvConf) fieldInitialized(f field) {
pt, ok := f.(*primitiveType)
pt, ok := f.(*fieldType)
if !ok {
return
}
Expand All @@ -45,7 +41,7 @@ func (e *EnvConf) fieldInitialized(f field) {
}

func (e *EnvConf) fieldDefined(f field) {
pt, ok := f.(*primitiveType)
pt, ok := f.(*fieldType)
if !ok {
return
}
Expand All @@ -68,7 +64,7 @@ func (e *EnvConf) fieldDefined(f field) {
}

func (e *EnvConf) fieldNotDefined(f field, err error) {
pt, ok := f.(*primitiveType)
pt, ok := f.(*fieldType)
if !ok {
return
}
Expand Down
10 changes: 10 additions & 0 deletions parser_primitives_test.go → parser_field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,13 @@ func TestParse_ValueWithSpace_Ok(t *testing.T) {
t.Errorf("incorrect result:%#v", cfg)
}
}

func TestParse_PrivateField_Ok(t *testing.T) {
cfg := struct {
field2 string `default:"f2"`
}{}

if err := envconf.Parse(&cfg); err != nil {
t.Fatal(err)
}
}
Loading

0 comments on commit 36d2b12

Please sign in to comment.