diff --git a/README.md b/README.md index e4f4f61..598c961 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,9 @@ name := s.Field("Name") // Get the underlying value, value => "gopher" value := name.Value().(string) +// Set the field's value +name.Set("another gopher") + // Check if the field is exported or not if name.IsExported() { fmt.Println("Name field is exported") diff --git a/field.go b/field.go index f916925..c557fb8 100644 --- a/field.go +++ b/field.go @@ -1,6 +1,15 @@ package structs -import "reflect" +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errNotExported = errors.New("field is not exported") + errNotSettable = errors.New("field is not settable") +) // Field represents a single struct field that encapsulates high level // functions around the field. @@ -45,6 +54,32 @@ func (f *Field) Name() string { return f.field.Name } +// Set sets the field to given value v. It retuns an error if the field is not +// settable (not addresable or not exported) or if the given value's type +// doesn't match the fields type. +func (f *Field) Set(val interface{}) error { + // needed to make the field settable + v := reflect.Indirect(f.value) + + if !f.IsExported() { + return errNotExported + } + + // do we get here? not sure... + if !v.CanSet() { + return errNotSettable + } + + given := reflect.ValueOf(val) + + if v.Kind() != given.Kind() { + return fmt.Errorf("wrong kind: %s want: %s", given.Kind(), v.Kind()) + } + + v.Set(given) + return nil +} + // Field returns the field from a nested struct. It panics if the nested struct // is not exported or if the field was not found. func (f *Field) Field(name string) *Field { diff --git a/field_test.go b/field_test.go index 090e8f2..96afa57 100644 --- a/field_test.go +++ b/field_test.go @@ -9,7 +9,9 @@ type Foo struct { C bool `json:"c"` d string // not exported x string `xml:"x"` // not exported, with tag - *Bar // embedded + Y []string + Z map[string]interface{} + *Bar // embedded } type Bar struct { @@ -30,12 +32,70 @@ func newStruct() *Struct { A: "gopher", C: true, d: "small", + Y: []string{"example"}, + Z: nil, } f.Bar = b return New(f) } +func TestField_Set(t *testing.T) { + s := newStruct() + + f := s.Field("A") + err := f.Set("fatih") + if err != nil { + t.Error(err) + } + + if f.Value().(string) != "fatih" { + t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") + } + + f = s.Field("Y") + err = f.Set([]string{"override", "with", "this"}) + if err != nil { + t.Error(err) + } + + sliceLen := len(f.Value().([]string)) + if sliceLen != 3 { + t.Errorf("Setted values slice length is wrong: %d, want: %d", sliceLen, 3) + } + + f = s.Field("C") + err = f.Set(false) + if err != nil { + t.Error(err) + } + + if f.Value().(bool) { + t.Errorf("Setted value is wrong: %s want: %s", f.Value().(bool), false) + } + + // let's pass a different type + f = s.Field("A") + err = f.Set(123) // Field A is of type string, but we are going to pass an integer + if err == nil { + t.Error("Setting a field's value with a different type than the field's type should return an error") + } + + // old value should be still there :) + if f.Value().(string) != "fatih" { + t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") + } + + // let's access an unexported field, which should give an error + f = s.Field("d") + err = f.Set("large") + if err != errNotExported { + t.Error(err) + } + + // TODO: let's access a non addresable field, which should give an error +} + func TestField(t *testing.T) { s := newStruct()