Merge pull request #3223 from liggitt/default_conversion

Make default conversion behavior available to conversion funcs
This commit is contained in:
Daniel Smith 2015-01-06 13:22:25 -08:00
commit 4b61dc84cb
2 changed files with 78 additions and 2 deletions

View File

@ -78,6 +78,10 @@ type Scope interface {
// parameters, you'll run out of stack space before anything useful happens.
Convert(src, dest interface{}, flags FieldMatchingFlags) error
// DefaultConvert performs the default conversion, without calling a conversion func
// on the current stack frame. This makes it safe to call from a conversion func.
DefaultConvert(src, dest interface{}, flags FieldMatchingFlags) error
// SrcTags and DestTags contain the struct tags that src and dest had, respectively.
// If the enclosing object was not a struct, then these will contain no tags, of course.
SrcTag() reflect.StructTag
@ -167,6 +171,12 @@ func (s *scope) Convert(src, dest interface{}, flags FieldMatchingFlags) error {
return s.converter.Convert(src, dest, flags, s.meta)
}
// DefaultConvert continues a conversion, performing a default conversion (no conversion func)
// for the current stack frame.
func (s *scope) DefaultConvert(src, dest interface{}, flags FieldMatchingFlags) error {
return s.converter.DefaultConvert(src, dest, flags, s.meta)
}
// SrcTag returns the tag of the struct containing the current source item, if any.
func (s *scope) SrcTag() reflect.StructTag {
return s.srcStack.top().tag
@ -296,6 +306,24 @@ func (f FieldMatchingFlags) IsSet(flag FieldMatchingFlags) bool {
// it is not used by Convert() other than storing it in the scope.
// Not safe for objects with cyclic references!
func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags, meta *Meta) error {
return c.doConversion(src, dest, flags, meta, c.convert)
}
// DefaultConvert will translate src to dest if it knows how. Both must be pointers.
// No conversion func is used. If the default copying mechanism
// doesn't work on this type pair, an error will be returned.
// Read the comments on the various FieldMatchingFlags constants to understand
// what the 'flags' parameter does.
// 'meta' is given to allow you to pass information to conversion functions,
// it is not used by DefaultConvert() other than storing it in the scope.
// Not safe for objects with cyclic references!
func (c *Converter) DefaultConvert(src, dest interface{}, flags FieldMatchingFlags, meta *Meta) error {
return c.doConversion(src, dest, flags, meta, c.defaultConvert)
}
type conversionFunc func(sv, dv reflect.Value, scope *scope) error
func (c *Converter) doConversion(src, dest interface{}, flags FieldMatchingFlags, meta *Meta, f conversionFunc) error {
dv, err := EnforcePtr(dest)
if err != nil {
return err
@ -315,7 +343,7 @@ func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags, met
// Leave something on the stack, so that calls to struct tag getters never fail.
s.srcStack.push(scopeStackElem{})
s.destStack.push(scopeStackElem{})
return c.convert(sv, dv, s)
return f(sv, dv, s)
}
// callCustom calls 'custom' with sv & dv. custom must be a conversion function.
@ -358,6 +386,14 @@ func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
return c.callCustom(sv, dv, fv, scope)
}
return c.defaultConvert(sv, dv, scope)
}
// defaultConvert recursively copies sv into dv. no conversion function is called
// for the current stack frame (but conversion functions may be called for nested objects)
func (c *Converter) defaultConvert(sv, dv reflect.Value, scope *scope) error {
dt, st := dv.Type(), sv.Type()
if !scope.flags.IsSet(AllowDifferentFieldTypeNames) && c.NameFunc(dt) != c.NameFunc(st) {
return scope.error("type names don't match (%v, %v)", c.NameFunc(st), c.NameFunc(dt))
}

View File

@ -25,6 +25,47 @@ import (
"github.com/google/gofuzz"
)
func TestConverter_DefaultConvert(t *testing.T) {
type A struct {
Foo string
Baz int
}
type B struct {
Bar string
Baz int
}
c := NewConverter()
c.Debug = t
c.NameFunc = func(t reflect.Type) string { return "MyType" }
// Ensure conversion funcs can call DefaultConvert to get default behavior,
// then fixup remaining fields manually
err := c.Register(func(in *A, out *B, s Scope) error {
if err := s.DefaultConvert(in, out, IgnoreMissingFields); err != nil {
return err
}
out.Bar = in.Foo
return nil
})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
x := A{"hello, intrepid test reader!", 3}
y := B{}
err = c.Convert(&x, &y, 0, nil)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if e, a := x.Foo, y.Bar; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := x.Baz, y.Baz; e != a {
t.Errorf("expected %v, got %v", e, a)
}
}
func TestConverter_CallsRegisteredFunctions(t *testing.T) {
type A struct {
Foo string
@ -86,7 +127,6 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = c.Convert(&A{}, &C{}, 0, nil)
if err == nil {
t.Errorf("unexpected non-error")