struct: support StructReflector interface (#221)

* StructReflector

* struct: add unit tests

* struct: avoid using map in tests
This commit is contained in:
ᴜɴᴋɴᴡᴏɴ
2020-01-31 20:26:21 +08:00
committed by GitHub
parent 94291fffe2
commit 7d14fca8d2
3 changed files with 81 additions and 20 deletions
+1 -1
View File
@@ -453,7 +453,7 @@ func (f *File) parse(reader io.Reader) (err error) {
section.Comment = strings.TrimSpace(p.comment.String())
// Reset aotu-counter and comments
// Reset auto-counter and comments
p.comment.Reset()
p.count = 1
+17 -9
View File
@@ -305,14 +305,13 @@ func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
if isAnonymous || isStruct || isStructPtr {
if sec, err := s.f.GetSection(fieldName); err == nil {
// Only set the field to non-nil struct value if we have
// a section for it. Otherwise, we end up with a non-nil
// struct ptr even though there is no data.
// Only set the field to non-nil struct value if we have a section for it.
// Otherwise, we end up with a non-nil struct ptr even though there is no data.
if isStructPtr && field.IsNil() {
field.Set(reflect.New(tpField.Type.Elem()))
}
if err = sec.mapTo(field, isStrict); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
return fmt.Errorf("error mapping field %q: %v", fieldName, err)
}
continue
}
@@ -320,7 +319,7 @@ func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
if key, err := s.GetKey(fieldName); err == nil {
delim := parseDelim(tpField.Tag.Get("delim"))
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
return fmt.Errorf("error mapping field %q: %v", fieldName, err)
}
}
}
@@ -512,6 +511,11 @@ func isEmptyValue(v reflect.Value) bool {
return false
}
// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
type StructReflector interface {
ReflectINIStruct(*File) error
}
func (s *Section) reflectFrom(val reflect.Value) error {
if val.Kind() == reflect.Ptr {
val = val.Elem()
@@ -532,6 +536,10 @@ func (s *Section) reflectFrom(val reflect.Value) error {
continue
}
if r, ok := field.Interface().(StructReflector); ok {
return r.ReflectINIStruct(s.f)
}
fieldName := s.parseFieldName(tpField.Name, rawName)
if len(fieldName) == 0 || !field.CanSet() {
continue
@@ -552,12 +560,11 @@ func (s *Section) reflectFrom(val reflect.Value) error {
}
if err = sec.reflectFrom(field); err != nil {
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
return fmt.Errorf("error reflecting field %q: %v", fieldName, err)
}
continue
}
// Note: Same reason as secion.
key, err := s.GetKey(fieldName)
if err != nil {
key, _ = s.NewKey(fieldName, "")
@@ -568,8 +575,9 @@ func (s *Section) reflectFrom(val reflect.Value) error {
key.Comment = tpField.Tag.Get("comment")
}
if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim")), allowShadow); err != nil {
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
delim := parseDelim(tpField.Tag.Get("delim"))
if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
return fmt.Errorf("error reflecting field %q: %v", fieldName, err)
}
}
+63 -10
View File
@@ -75,7 +75,7 @@ type testStruct struct {
DurationPtrNil *time.Duration
}
const _CONF_DATA_STRUCT = `
const confDataStruct = `
NAME = Unknwon
Age = 21
Male = true
@@ -157,7 +157,7 @@ type fooBar struct {
Here, When string
}
const _INVALID_DATA_CONF_STRUCT = `
const invalidDataConfStruct = `
Name =
Age = age
Male = 123
@@ -170,7 +170,7 @@ func Test_MapToStruct(t *testing.T) {
Convey("Map to struct", t, func() {
Convey("Map file to struct", func() {
ts := new(testStruct)
So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil)
So(ts.Name, ShouldEqual, "Unknwon")
So(ts.Age, ShouldEqual, 21)
@@ -230,7 +230,7 @@ func Test_MapToStruct(t *testing.T) {
Convey("Map section to struct", func() {
foobar := new(fooBar)
f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
f, err := ini.Load([]byte(confDataStruct))
So(err, ShouldBeNil)
So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
@@ -239,7 +239,7 @@ func Test_MapToStruct(t *testing.T) {
})
Convey("Map to non-pointer struct", func() {
f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
f, err := ini.Load([]byte(confDataStruct))
So(err, ShouldBeNil)
So(f, ShouldNotBeNil)
@@ -247,7 +247,7 @@ func Test_MapToStruct(t *testing.T) {
})
Convey("Map to unsupported type", func() {
f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
f, err := ini.Load([]byte(confDataStruct))
So(err, ShouldBeNil)
So(f, ShouldNotBeNil)
@@ -264,13 +264,13 @@ func Test_MapToStruct(t *testing.T) {
Convey("Map to omitempty field", func() {
ts := new(testStruct)
So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
So(ini.MapTo(ts, []byte(confDataStruct)), ShouldBeNil)
So(ts.Omitted, ShouldEqual, true)
})
Convey("Map with shadows", func() {
f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(confDataStruct))
So(err, ShouldBeNil)
ts := new(testStruct)
So(f.MapTo(ts), ShouldBeNil)
@@ -284,7 +284,7 @@ func Test_MapToStruct(t *testing.T) {
})
Convey("Map to wrong types and gain default values", func() {
f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT))
f, err := ini.Load([]byte(invalidDataConfStruct))
So(err, ShouldBeNil)
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
@@ -473,7 +473,7 @@ func Test_NameGetter(t *testing.T) {
So(err, ShouldBeNil)
So(cfg, ShouldNotBeNil)
cfg.NameMapper = ini.AllCapsUnderscore
cfg.NameMapper = ini.SnackCase
tg := new(testMapper)
So(cfg.MapTo(tg), ShouldBeNil)
So(tg.PackageName, ShouldEqual, "ini")
@@ -494,3 +494,56 @@ func Test_Duration(t *testing.T) {
So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds())
})
}
type Employer struct {
Name string
Title string
}
type Employers []*Employer
func (es Employers) ReflectINIStruct(f *ini.File) error {
for _, e := range es {
f.Section(e.Name).Key("Title").SetValue(e.Title)
}
return nil
}
// Inspired by https://github.com/go-ini/ini/issues/199
func Test_StructReflector(t *testing.T) {
Convey("Reflect with StructReflector interface", t, func() {
p := &struct {
FirstName string
Employer Employers
}{
FirstName: "Andrew",
Employer: []*Employer{
{
Name: `Employer "VMware"`,
Title: "Staff II Engineer",
},
{
Name: `Employer "EMC"`,
Title: "Consultant Engineer",
},
},
}
f := ini.Empty()
So(f.ReflectFrom(p), ShouldBeNil)
var buf bytes.Buffer
_, err := f.WriteTo(&buf)
So(err, ShouldBeNil)
So(buf.String(), ShouldEqual, `FirstName = Andrew
[Employer "VMware"]
Title = Staff II Engineer
[Employer "EMC"]
Title = Consultant Engineer
`)
})
}