-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from antonmashko/external-source-refactoring
flexible logic for external source
- Loading branch information
Showing
9 changed files
with
365 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,113 @@ | ||
package envconf | ||
|
||
import "reflect" | ||
|
||
// DEPRECATED: on next changes this interface will be removed | ||
type Value interface { | ||
Owner() Value | ||
Name() string | ||
Tag() reflect.StructField | ||
} | ||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
"unicode" | ||
) | ||
|
||
// External config source | ||
type External interface { | ||
// Get string value from values chain(from parent to child) | ||
Get(...Value) (interface{}, bool) | ||
// | ||
TagName() string | ||
Unmarshal(interface{}) error | ||
} | ||
|
||
type emptyExt struct{} | ||
|
||
func (c *emptyExt) Get(v ...Value) (interface{}, bool) { return nil, false } | ||
func (emptyExt) TagName() string { | ||
return "" | ||
} | ||
|
||
func (emptyExt) Unmarshal(v interface{}) error { return nil } | ||
|
||
type externalConfig struct { | ||
s *structType | ||
ext External | ||
data map[string]interface{} | ||
} | ||
|
||
func newExternalConfig(ext External) *externalConfig { | ||
return &externalConfig{ | ||
ext: ext, | ||
data: make(map[string]interface{}), | ||
} | ||
} | ||
|
||
func (c *externalConfig) Unmarshal(v interface{}) error { | ||
if c.ext == (emptyExt{}) { | ||
return nil | ||
} | ||
mp := make(map[string]interface{}) | ||
err := c.ext.Unmarshal(&mp) | ||
if err != nil { | ||
return err | ||
} | ||
c.data = make(map[string]interface{}) | ||
if err = c.fillMap(c.s, mp); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (c *externalConfig) setParentStruct(s *structType) { | ||
c.s = s | ||
} | ||
|
||
func (c *externalConfig) get(f field) (interface{}, bool) { | ||
v, ok := c.data[fullname(f)] | ||
return v, ok | ||
} | ||
|
||
func (c *emptyExt) Unmarshal(v interface{}) error { return nil } | ||
func (c *externalConfig) fillMap(s *structType, src map[string]interface{}) error { | ||
for k, v := range src { | ||
f, ok := c.findField(k, s) | ||
if !ok { | ||
continue | ||
} | ||
|
||
mp, ok := v.(map[string]interface{}) | ||
if ok && f.structField().Type.Kind() != reflect.Map { | ||
st, ok := f.(*structType) | ||
if !ok { | ||
return &Error{ | ||
Message: fmt.Sprintf("unable to cast %s to struct", f.structField().Type), | ||
FieldName: fullname(f), | ||
} | ||
} | ||
c.fillMap(st, mp) | ||
continue | ||
} | ||
|
||
c.data[fullname(f)] = v | ||
} | ||
return nil | ||
} | ||
|
||
func (c *externalConfig) findField(key string, s *structType) (field, bool) { | ||
var fr rune | ||
for _, r := range key { | ||
fr = r | ||
break | ||
} | ||
lc := unicode.IsLower(fr) | ||
for _, f := range s.fields { | ||
// if annotation exists matching only by it | ||
sf := f.structField() | ||
extName := sf.Tag.Get(c.ext.TagName()) | ||
if extName != "" && key == extName { | ||
return f, true | ||
} | ||
|
||
// unexportable field. looking for any first match with EqualFold | ||
if lc && strings.EqualFold(key, sf.Name) { | ||
return f, true | ||
} | ||
|
||
if key == sf.Name { | ||
return f, true | ||
} | ||
} | ||
|
||
return nil, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package envconf | ||
|
||
import "testing" | ||
|
||
func TestExternal_EmpExt_Ok(t *testing.T) { | ||
e := emptyExt{} | ||
if e.TagName() != "" || e.Unmarshal(nil) != nil { | ||
t.Error("unexpected result") | ||
} | ||
} | ||
|
||
func TestExternal_newExternalConfig_Ok(t *testing.T) { | ||
ext := newExternalConfig(emptyExt{}) | ||
if ext == nil { | ||
t.Fail() | ||
} | ||
if ext.Unmarshal(nil) != nil { | ||
t.Error("unexpected result") | ||
} | ||
} | ||
|
||
func TestExternal_InvalidJson_Err(t *testing.T) { | ||
jsonConf := NewJsonConfig() | ||
jsonConf.Read([]byte("<test></test>")) | ||
ext := newExternalConfig(jsonConf) | ||
tc := struct { | ||
Foo int | ||
}{} | ||
if ext.Unmarshal(&tc) == nil { | ||
t.Error("unexpected error got nil") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.