diff --git a/args.go b/args.go index 01e9ab9..1121080 100644 --- a/args.go +++ b/args.go @@ -8,7 +8,7 @@ import ( ) func parseArgs(args []string, dest interface{}) (*Object, error) { - reqs, err := requirements(dest) + reqs, err := requirements(reflect.TypeOf(dest)) if err != nil { return nil, fmt.Errorf("unable to parse args: bad requirements: %s", err) } @@ -125,7 +125,7 @@ func parseArgs(args []string, dest interface{}) (*Object, error) { } func showHelp(dest interface{}) { - reqs, err := requirements(dest) + reqs, err := requirements(reflect.TypeOf(dest)) if err != nil { panic(err) } diff --git a/object.go b/object.go index 41abfb3..d4475a6 100644 --- a/object.go +++ b/object.go @@ -122,32 +122,69 @@ func (o *Object) Fill(dest interface{}) error { // dt = destination type dt := reflect.TypeOf(dest) if dt.Kind() != reflect.Ptr { - return fmt.Errorf("destination is of type %v; a pointer type is required", dt) + return fmt.Errorf("destination is of type %v (%v); a pointer type is required", dt, dt.Kind()) + } + + // ensure the pointer points to a struct type + if dt.Elem().Kind() != reflect.Struct { + return fmt.Errorf("destination is a pointer to a non-struct type: %v (pointer to struct required)", dt.Elem()) } - reqs, err := requirements(dest) + // value of our struct pointer + pv := reflect.ValueOf(dest) + + // value of the struct being pointed to + v := pv.Elem() + + return o.fillValue(v) +} + +func (o *Object) fillValue(dv reflect.Value) error { + switch dv.Kind() { + case reflect.Struct: + // this is fine + case reflect.Ptr: + if dv.IsNil() { + dv.Set(reflect.New(dv.Type().Elem())) + } + dv = reflect.Indirect(dv) + default: + return fmt.Errorf("moon object can only fillValue to a struct value, saw %v (%v)", dv.Type(), dv.Kind()) + } + + // the destination defines the requirements (i.e., the method of unpacking + // our moon data) + reqs, err := requirements(dv.Type()) if err != nil { - return fmt.Errorf("unable to gather requirements: %s", err) + return fmt.Errorf("unable to gather requirements: %v", err) } - // dv = destination value - dv := reflect.ValueOf(dest).Elem() for fname, req := range reqs { - // fv = field value + // field value fv := dv.FieldByName(fname) - v, ok := o.items[req.name] - if ok { - if !fv.Type().AssignableTo(reflect.TypeOf(v)) { - return fmt.Errorf("unable to assign field %s: source type %v is not assignable to destination type %v", req.name, fv.Type(), reflect.TypeOf(v)) - } - fv.Set(reflect.ValueOf(v)) - } else { + // object value + ov, ok := o.items[req.name] + if !ok { + // moon data is missing expected field if req.required { + // if the field is required, that's an error return fmt.Errorf("required field missing: %s", fname) } if req.d_fault != nil { + // otherwise, we look for a user-defined default value fv.Set(reflect.ValueOf(req.d_fault)) } + continue + } + + switch t_ov := ov.(type) { + case *Object: + return t_ov.fillValue(fv) + default: + if !fv.Type().AssignableTo(reflect.TypeOf(ov)) { + return fmt.Errorf("unable to assign field %s: source type %v is not assignable to destination type %v", req.name, reflect.TypeOf(ov), fv.Type()) + } + fv.Set(reflect.ValueOf(ov)) } } return nil diff --git a/object_test.go b/object_test.go index 9c6d49b..0fbdfde 100644 --- a/object_test.go +++ b/object_test.go @@ -141,23 +141,32 @@ func ExampleDoc_Get_two() { // Output: sean } -// func TestFillEmbeds(t *testing.T) { -// in := `top: {val: some_data}` -// -// var dest struct { -// Top *struct { -// Val string `name: val` -// } `name: top` -// } -// -// doc, err := ReadString(in) -// if err != nil { -// t.Error(err) -// return -// } -// -// if err := doc.Fill(&dest); err != nil { -// t.Error(err) -// return -// } -// } +func TestFillEmbeds(t *testing.T) { + in := `top: {val: some_data}` + + var dest struct { + Top *struct { + Val string `name: val` + } `name: top` + } + + doc, err := ReadString(in) + if err != nil { + t.Error(err) + return + } + + if err := doc.Fill(&dest); err != nil { + t.Error(err) + return + } + + if dest.Top == nil { + t.Error("didn't actually set a value") + return + } + + if dest.Top.Val != "some_data" { + t.Errorf("expected some_data, got %v", dest.Top.Val) + } +} diff --git a/req.go b/req.go index 328abc6..4c63d8e 100644 --- a/req.go +++ b/req.go @@ -87,18 +87,22 @@ func field2req(field reflect.StructField) (*req, error) { return &req, nil } -func requirements(dest interface{}) (map[string]req, error) { - dt := reflect.TypeOf(dest) - if dt.Kind() != reflect.Ptr { - return nil, fmt.Errorf("destination is of type %v; a pointer type is required", dt) +// requirements gathers the moon requirements for a given struct type. the +// output is a mapping of field names to requirements. +func requirements(t reflect.Type) (map[string]req, error) { + switch t.Kind() { + case reflect.Ptr: + t = t.Elem() + case reflect.Struct: + default: + return nil, fmt.Errorf("destination is of type %v; a pointer or struct type is required", t) } - dv := reflect.ValueOf(dest).Elem() - n := dv.NumField() - out := make(map[string]req, dv.NumField()) + n := t.NumField() + out := make(map[string]req, t.NumField()) for i := 0; i < n; i++ { - field := dv.Type().Field(i) + field := t.Field(i) req, err := field2req(field) if err != nil { return nil, fmt.Errorf("unable to gather requirements for field %s: %s", field.Name, err)