mirror of
https://github.com/go-ini/ini.git
synced 2026-06-19 21:46:45 +02:00
struct: support StructReflector interface (#221)
* StructReflector * struct: add unit tests * struct: avoid using map in tests
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
`)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user