From 2da1865fc23222c6d0bfeef4b446dccfd5e9ba83 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 23 Dec 2014 23:52:17 -0500 Subject: [PATCH] Make default conversion behavior available to conversion funcs --- pkg/conversion/converter.go | 38 ++++++++++++++++++++++++++++- pkg/conversion/converter_test.go | 42 +++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/pkg/conversion/converter.go b/pkg/conversion/converter.go index 9b2f6a2a1f5..0e04a602b55 100644 --- a/pkg/conversion/converter.go +++ b/pkg/conversion/converter.go @@ -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)) } diff --git a/pkg/conversion/converter_test.go b/pkg/conversion/converter_test.go index a05a15dfa5d..d1b3e2ba034 100644 --- a/pkg/conversion/converter_test.go +++ b/pkg/conversion/converter_test.go @@ -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")