You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ac...@apache.org on 2015/03/13 20:27:56 UTC

qpid-proton git commit: PROTON-827: Marshal/unmarshal maps and lists.

Repository: qpid-proton
Updated Branches:
  refs/heads/master 7e42628ed -> faf925c4a


PROTON-827: Marshal/unmarshal maps and lists.


Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/faf925c4
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/faf925c4
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/faf925c4

Branch: refs/heads/master
Commit: faf925c4afe02da2dced7a6592586b575ceea2ec
Parents: 7e42628
Author: Alan Conway <ac...@redhat.com>
Authored: Thu Mar 12 17:12:44 2015 -0400
Committer: Alan Conway <ac...@redhat.com>
Committed: Fri Mar 13 15:25:57 2015 -0400

----------------------------------------------------------------------
 .../bindings/go/src/apache.org/proton/error.go  |  55 ++--
 .../go/src/apache.org/proton/interop_test.go    | 175 ++++--------
 .../go/src/apache.org/proton/marshal.go         | 115 +++++---
 .../bindings/go/src/apache.org/proton/types.go  |  33 ++-
 .../go/src/apache.org/proton/unmarshal.go       | 286 ++++++++++++-------
 5 files changed, 384 insertions(+), 280 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/faf925c4/proton-c/bindings/go/src/apache.org/proton/error.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/error.go b/proton-c/bindings/go/src/apache.org/proton/error.go
index 63426de..f9cc948 100644
--- a/proton-c/bindings/go/src/apache.org/proton/error.go
+++ b/proton-c/bindings/go/src/apache.org/proton/error.go
@@ -25,6 +25,8 @@ import "C"
 
 import (
 	"fmt"
+	"reflect"
+	"runtime"
 )
 
 var pnErrorNames = map[int]string{
@@ -39,33 +41,35 @@ var pnErrorNames = map[int]string{
 	C.PN_INPROGRESS: "in progress",
 }
 
-func nonBlank(a, b string) string {
-	if a == "" {
-		return b
+func pnErrorName(code int) string {
+	name := pnErrorNames[code]
+	if name != "" {
+		return name
+	} else {
+		return "unknown error code"
 	}
-	return a
 }
 
-func pnErrorName(code int) string {
-	return nonBlank(pnErrorNames[code], "unknown error code")
+type BadUnmarshal struct {
+	AMQPType C.pn_type_t
+	GoType   reflect.Type
 }
 
-///
-// NOTE: pnError has String() and Error() methods.
-// The String() method prints the plain error message, the Error() method
-// prints the error message with a "proton:" prefix.
-// Thus you can format nested error messages with "%s" without getting nested "proton:"
-// prefixes but the prefix will be added when the end user uses Error()
-// or "%v" on the error value.
-//
-type pnError string
+func newBadUnmarshal(pnType C.pn_type_t, v interface{}) *BadUnmarshal {
+	return &BadUnmarshal{pnType, reflect.TypeOf(v)}
+}
 
-func (err pnError) String() string { return string(err) }
-func (err pnError) Error() string  { return fmt.Sprintf("proton: %s", string(err)) }
+func (e BadUnmarshal) Error() string {
+	if e.GoType.Kind() != reflect.Ptr {
+		return fmt.Sprintf("proton: cannot unmarshal to type %s, not a pointer", e.GoType)
+	} else {
+		return fmt.Sprintf("proton: cannot unmarshal AMQP %s to %s", getPnType(e.AMQPType), e.GoType)
+	}
+}
 
 // errorf creates an error with a formatted message
 func errorf(format string, a ...interface{}) error {
-	return pnError(fmt.Sprintf(format, a...))
+	return fmt.Errorf("proton: %s", fmt.Sprintf(format, a...))
 }
 
 func pnDataError(data *C.pn_data_t) string {
@@ -75,3 +79,18 @@ func pnDataError(data *C.pn_data_t) string {
 	}
 	return ""
 }
+
+// doRecover is called to recover from internal panics
+func doRecover(err *error) {
+	r := recover()
+	switch r := r.(type) {
+	case nil:
+		return
+	case runtime.Error:
+		panic(r)
+	case error:
+		*err = r
+	default:
+		panic(r)
+	}
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/faf925c4/proton-c/bindings/go/src/apache.org/proton/interop_test.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/interop_test.go b/proton-c/bindings/go/src/apache.org/proton/interop_test.go
index 5333055..c51d74a 100644
--- a/proton-c/bindings/go/src/apache.org/proton/interop_test.go
+++ b/proton-c/bindings/go/src/apache.org/proton/interop_test.go
@@ -20,9 +20,6 @@ under the License.
 // Test that conversion of Go type to/from AMQP is compatible with other
 // bindings.
 //
-// FIXME aconway 2015-03-01: this should move to proton/tests/go when we integrate
-// better with the proton build system.
-//
 package proton
 
 import (
@@ -37,7 +34,7 @@ import (
 
 func assertEqual(want interface{}, got interface{}) {
 	if !reflect.DeepEqual(want, got) {
-		panic(errorf("%T(%v) != %T(%v)", want, want, got, got))
+		panic(errorf("%#v != %#v", want, got))
 	}
 }
 
@@ -144,38 +141,35 @@ func TestPrimitivesCompatible(t *testing.T) {
 }
 
 // assertDecodeValue: want is the expected value, decode into a reflect.Value
-func assertDecodeValue(d *Decoder, want interface{}) {
+func assertDecodeInterface(d *Decoder, want interface{}) {
 
-	var v reflect.Value
-	assertNil(d.Decode(&v))
+	var got, got2 interface{}
+	assertNil(d.Decode(&got))
 
-	got := v.Interface()
 	assertEqual(want, got)
 
 	// Try round trip encoding
-	bytes, err := Marshal(v)
+	bytes, err := Marshal(got)
 	assertNil(err)
-	n, err := Unmarshal(bytes, &v)
+	n, err := Unmarshal(bytes, &got2)
 	assertNil(err)
 	assertEqual(n, len(bytes))
-	got = v.Interface()
-	assertEqual(want, got)
+	assertEqual(want, got2)
 }
 
-func TestPrimitivesValue(t *testing.T) {
+func TestPrimitivesInterface(t *testing.T) {
 	d := NewDecoder(getReader("primitives"))
-	// Decoding into reflect.Value
-	assertDecodeValue(d, true)
-	assertDecodeValue(d, false)
-	assertDecodeValue(d, uint8(42))
-	assertDecodeValue(d, uint16(42))
-	assertDecodeValue(d, int16(-42))
-	assertDecodeValue(d, uint32(12345))
-	assertDecodeValue(d, int32(-12345))
-	assertDecodeValue(d, uint64(12345))
-	assertDecodeValue(d, int64(-12345))
-	assertDecodeValue(d, float32(0.125))
-	assertDecodeValue(d, float64(0.125))
+	assertDecodeInterface(d, true)
+	assertDecodeInterface(d, false)
+	assertDecodeInterface(d, uint8(42))
+	assertDecodeInterface(d, uint16(42))
+	assertDecodeInterface(d, int16(-42))
+	assertDecodeInterface(d, uint32(12345))
+	assertDecodeInterface(d, int32(-12345))
+	assertDecodeInterface(d, uint64(12345))
+	assertDecodeInterface(d, int64(-12345))
+	assertDecodeInterface(d, float32(0.125))
+	assertDecodeInterface(d, float64(0.125))
 }
 
 func TestStrings(t *testing.T) {
@@ -209,6 +203,9 @@ func TestStrings(t *testing.T) {
 	d = NewDecoder(getReader("strings"))
 	var s string
 	err := d.Decode(s)
+	if err == nil {
+		t.Fatal("Expected error")
+	}
 	if !strings.Contains(err.Error(), "not a pointer") {
 		t.Error(err)
 	}
@@ -217,6 +214,14 @@ func TestStrings(t *testing.T) {
 	if !strings.Contains(err.Error(), "cannot unmarshal") {
 		t.Error(err)
 	}
+	_, err = Unmarshal([]byte{}, nil)
+	if !strings.Contains(err.Error(), "not enough data") {
+		t.Error(err)
+	}
+	_, err = Unmarshal([]byte("foobar"), nil)
+	if !strings.Contains(err.Error(), "invalid argument") {
+		t.Error(err)
+	}
 }
 
 func TestEncodeDecode(t *testing.T) {
@@ -226,9 +231,10 @@ func TestEncodeDecode(t *testing.T) {
 		u8 uint8
 		b  bool
 		f  float32
+		v  interface{}
 	}
 
-	in := data{"foo", 42, 9, true, 1.234}
+	in := data{"foo", 42, 9, true, 1.234, "thing"}
 
 	buf := bytes.Buffer{}
 	e := NewEncoder(&buf)
@@ -237,6 +243,7 @@ func TestEncodeDecode(t *testing.T) {
 	e.Encode(in.u8)
 	e.Encode(in.b)
 	e.Encode(in.f)
+	e.Encode(in.v)
 
 	var out data
 	d := NewDecoder(&buf)
@@ -245,103 +252,39 @@ func TestEncodeDecode(t *testing.T) {
 	d.Decode(&out.u8)
 	d.Decode(&out.b)
 	d.Decode(&out.f)
+	d.Decode(&out.v)
 
 	assertEqual(in, out)
-
-	vIn := reflect.ValueOf("thing")
-	e.Encode(vIn)
-	var vOut reflect.Value
-	d.Decode(&vOut)
-	assertEqual("thing", vOut.Interface())
 }
 
-func BenchmarkDecode(b *testing.B) {
-	var buf bytes.Buffer
-	for _, f := range []string{"strings", "primitives"} {
-		_, err := buf.ReadFrom(getReader(f))
-		if err != nil {
-			panic(err)
-		}
-	}
+func TestMap(t *testing.T) {
+	d := NewDecoder(getReader("maps"))
 
-	d := NewDecoder(bytes.NewReader(buf.Bytes()))
+	// Generic map
+	var m Map
+	assertDecode(d, Map{"one": int32(1), "two": int32(2), "three": int32(3)}, &m)
 
-	decode := func(v interface{}) {
-		err := d.Decode(v)
-		if err != nil {
-			panic(err)
-		}
-	}
-
-	for i := 0; i < b.N; i++ {
-		var by []byte
-		// strings
-		decode(&by)
-		var s string
-		decode(&s)
-		decode(&s)
-		decode(&by)
-		decode(&s)
-		decode(&s)
-		// primitives
-		var b bool
-		decode(&b)
-		decode(&b)
-		var u8 uint8
-		decode(&u8)
-		var u16 uint16
-		decode(&u16)
-		var i16 int16
-		decode(&i16)
-		var u32 uint32
-		decode(&u32)
-		var i32 int32
-		decode(&i32)
-		var u64 uint64
-		decode(&u64)
-		var i64 int64
-		decode(&i64)
-		var f32 float32
-		decode(&f32)
-		var f64 float64
-		decode(&f64)
-
-		d = NewDecoder(bytes.NewReader(buf.Bytes()))
-	}
-}
+	// Interface as map
+	var i interface{}
+	assertDecode(d, Map{int32(1): "one", int32(2): "two", int32(3): "three"}, &i)
 
-func BenchmarkEncode(b *testing.B) {
+	d = NewDecoder(getReader("maps"))
+	// Specific typed map
+	var m2 map[string]int
+	assertDecode(d, map[string]int{"one": 1, "two": 2, "three": 3}, &m2)
 
-	var buf bytes.Buffer
-	buf.Grow(10000) // Avoid buffer reallocation during benchmark
-	e := NewEncoder(&buf)
-	encode := func(v interface{}) {
-		err := e.Encode(v)
-		if err != nil {
-			panic(err)
-		}
-	}
+	// Round trip a nested map
+	m = Map{int64(1): "one", "two": int32(2), true: Map{uint8(1): true, uint8(2): false}}
+	bytes, err := Marshal(m)
+	assertNil(err)
+	_, err = Unmarshal(bytes, &i)
+	assertNil(err)
+	assertEqual(m, i)
+}
 
-	for i := 0; i < b.N; i++ {
-		// strings
-		encode([]byte("foo"))
-		encode("foo")
-		encode("bar")
-		encode([]byte(""))
-		encode("")
-		encode("")
-		// primitives
-		encode(true)
-		encode(false)
-		encode(uint8(42))
-		encode(int8(-42))
-		encode(uint16(12345))
-		encode(int16(-12345))
-		encode(uint32(123453245))
-		encode(int32(-123453245))
-		encode(uint64(123456445))
-		encode(int64(-123456445))
-		encode(float32(1.2345))
-		encode(float64(1.23456))
-	}
+func TestList(t *testing.T) {
+	d := NewDecoder(getReader("lists"))
+	var l List
+	assertDecode(d, List{int32(32), "foo", true}, &l)
+	assertDecode(d, List{}, &l)
 }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/faf925c4/proton-c/bindings/go/src/apache.org/proton/marshal.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/marshal.go b/proton-c/bindings/go/src/apache.org/proton/marshal.go
index 957e0cf..674a25f 100644
--- a/proton-c/bindings/go/src/apache.org/proton/marshal.go
+++ b/proton-c/bindings/go/src/apache.org/proton/marshal.go
@@ -35,21 +35,31 @@ Marshal encodes a value as AMQP.
 
 Go types are encoded as follows
 
-bool to AMQP bool.
-
-int, int8, int16, int32, int64 to equivalent AMQP signed integer type.
-
-uint, uint8, uint16, uint32, uint64 to equivalent or smaller AMQP unsigned integer type.
-
-float32, float64 to AMQP float or double.
-
-string to AMQP string.
-
-[]byte to AMQP binary.
+ +-------------------------------------+--------------------------------------------+
+ |Go type                              |AMQP type                                   |
+ +-------------------------------------+--------------------------------------------+
+ |bool                                 |bool                                        |
+ +-------------------------------------+--------------------------------------------+
+ |int8, int16, int32, int64 (int)      |byte, short, int, long (int or long)        |
+ +-------------------------------------+--------------------------------------------+
+ |uint8, uint16, uint32, uint64 (uint) |ubyte, ushort, uint, ulong (uint or ulong)  |
+ +-------------------------------------+--------------------------------------------+
+ |float32, float64                     |float, double.                              |
+ +-------------------------------------+--------------------------------------------+
+ |string                               |string                                      |
+ +-------------------------------------+--------------------------------------------+
+ |[]byte                               |binary                                      |
+ +-------------------------------------+--------------------------------------------+
+ |interface{}                          |as to the contained type                    |
+ +-------------------------------------+--------------------------------------------+
+ |map[K]T                              |map with K and T converted as above         |
+ +-------------------------------------+--------------------------------------------+
+ |Map                                  |map may have mixed types for keys and values|
+ +-------------------------------------+--------------------------------------------+
 
 TODO types
 
-Go: array, slice, struct, map, reflect/Value
+Go: array, slice, struct
 
 Go types that cannot be marshaled
 
@@ -57,17 +67,32 @@ complex64/128, uintptr, function, interface, channel
 
 */
 func Marshal(v interface{}) (bytes []byte, err error) {
-	defer func() {
-		if x := recover(); x != nil {
-			err = errorf("%v", x)
-		}
-	}()
 	return marshal(make([]byte, minEncode), v)
 }
 
 func marshal(bytesIn []byte, v interface{}) (bytes []byte, err error) {
+	defer doRecover(&err)
 	data := C.pn_data(0)
 	defer C.pn_data_free(data)
+	put(data, v)
+	// FIXME aconway 2015-03-11: get size from proton.
+	bytes = bytesIn
+	for {
+		n := int(C.pn_data_encode(data, (*C.char)(unsafe.Pointer(&bytes[0])), C.size_t(cap(bytes))))
+		if n != int(C.PN_EOS) {
+			if n < 0 {
+				err = errorf(pnErrorName(n))
+			} else {
+				bytes = bytes[0:n]
+			}
+			return
+		}
+		bytes = make([]byte, cap(bytes)*2)
+	}
+	return
+}
+
+func put(data *C.pn_data_t, v interface{}) {
 	switch v := v.(type) {
 	case bool:
 		C.pn_data_put_bool(data, C.bool(v))
@@ -107,28 +132,52 @@ func marshal(bytesIn []byte, v interface{}) (bytes []byte, err error) {
 		C.pn_data_put_string(data, toPnBytes([]byte(v)))
 	case []byte:
 		C.pn_data_put_binary(data, toPnBytes(v))
-	case reflect.Value:
-		return marshal(bytesIn, v.Interface())
+	case Map: // Special map type
+		C.pn_data_put_map(data)
+		C.pn_data_enter(data)
+		for key, val := range v {
+			put(data, key)
+			put(data, val)
+		}
+		C.pn_data_exit(data)
 	default:
-		panic(errorf("cannot marshal %s to AMQP", reflect.TypeOf(v)))
-	}
-	// FIXME aconway 2015-03-11: get size from proton.
-	bytes = bytesIn
-	for {
-		n := int(C.pn_data_encode(data, (*C.char)(unsafe.Pointer(&bytes[0])), C.size_t(cap(bytes))))
-		if n != int(C.PN_EOS) {
-			if n < 0 {
-				err = errorf(pnErrorName(n))
-			} else {
-				bytes = bytes[0:n]
-			}
-			return
+		switch reflect.TypeOf(v).Kind() {
+		case reflect.Map:
+			putMap(data, v)
+		case reflect.Slice:
+			putList(data, v)
+		default:
+			panic(errorf("cannot marshal %s to AMQP", reflect.TypeOf(v)))
 		}
-		bytes = make([]byte, cap(bytes)*2)
+	}
+	err := pnDataError(data)
+	if err != "" {
+		panic(errorf("marshal %s", err))
 	}
 	return
 }
 
+func putMap(data *C.pn_data_t, v interface{}) {
+	mapValue := reflect.ValueOf(v)
+	C.pn_data_put_map(data)
+	C.pn_data_enter(data)
+	for _, key := range mapValue.MapKeys() {
+		put(data, key.Interface())
+		put(data, mapValue.MapIndex(key).Interface())
+	}
+	C.pn_data_exit(data)
+}
+
+func putList(data *C.pn_data_t, v interface{}) {
+	listValue := reflect.ValueOf(v)
+	C.pn_data_put_list(data)
+	C.pn_data_enter(data)
+	for i := 0; i < listValue.Len(); i++ {
+		put(data, listValue.Index(i).Interface())
+	}
+	C.pn_data_exit(data)
+}
+
 // Encoder encodes AMQP values to an io.Writer
 type Encoder struct {
 	writer io.Writer

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/faf925c4/proton-c/bindings/go/src/apache.org/proton/types.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/types.go b/proton-c/bindings/go/src/apache.org/proton/types.go
index 84d0d90..f8c5560 100644
--- a/proton-c/bindings/go/src/apache.org/proton/types.go
+++ b/proton-c/bindings/go/src/apache.org/proton/types.go
@@ -23,6 +23,8 @@ package proton
 import "C"
 
 import (
+	"bytes"
+	"fmt"
 	"reflect"
 )
 
@@ -33,13 +35,6 @@ type pnPut func(data *C.pn_data_t, v interface{})
 type pnType struct {
 	code C.pn_type_t // AMQP type code
 	name string      // AMQP type name
-
-	// unmarshal data
-	get     pnGet        // pn_data_*_get function wrapper returning a reflect.Value
-	getType reflect.Type // go type for unmarshaling into a Value
-
-	// marshal data
-	put pnPut // pn_data_*_put function wrapper taking an interface{}
 }
 
 func (pt *pnType) String() string { return pt.name }
@@ -116,3 +111,27 @@ var (
 	bytesType = reflect.TypeOf([]byte{})
 	valueType = reflect.TypeOf(reflect.Value{})
 )
+
+// Map is a generic map that can have mixed key and value types and so can represent any AMQP map
+//
+type Map map[interface{}]interface{}
+
+// List is a generic list that can hold mixed values and can represent any AMQP list.
+//
+type List []interface{}
+
+// GoString for Map prints values with their types, useful for debugging.
+func (m Map) GoString() string {
+	out := &bytes.Buffer{}
+	fmt.Fprintf(out, "%s{", reflect.TypeOf(m))
+	i := len(m)
+	for k, v := range m {
+		fmt.Fprintf(out, "%T(%#v): %T(%#v)", k, k, v, v)
+		i--
+		if i > 0 {
+			fmt.Fprint(out, ", ")
+		}
+	}
+	fmt.Fprint(out, "}")
+	return out.String()
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/faf925c4/proton-c/bindings/go/src/apache.org/proton/unmarshal.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/unmarshal.go b/proton-c/bindings/go/src/apache.org/proton/unmarshal.go
index 4255b28..a003019 100644
--- a/proton-c/bindings/go/src/apache.org/proton/unmarshal.go
+++ b/proton-c/bindings/go/src/apache.org/proton/unmarshal.go
@@ -67,11 +67,7 @@ func (d *Decoder) Buffered() io.Reader {
 // See the documentation for Unmarshal for details about the conversion of AMQP into a Go value.
 //
 func (d *Decoder) Decode(v interface{}) (err error) {
-	defer func() {
-		if x := recover(); x != nil {
-			err = errorf("%v", x)
-		}
-	}()
+	defer doRecover(&err)
 	data := C.pn_data(0)
 	defer C.pn_data_free(data)
 	var n int
@@ -90,35 +86,59 @@ func (d *Decoder) Decode(v interface{}) (err error) {
 
 /*
 Unmarshal decodes AMQP-encoded bytes and stores the result in the value pointed to by v.
+Types are converted as follows:
+
+ +---------------------------+----------------------------------------------------------------------+
+ |To Go types                |From AMQP types                                                       |
+ +===========================+======================================================================+
+ |bool                       |bool                                                                  |
+ +---------------------------+----------------------------------------------------------------------+
+ |int, int8, int16,          |Equivalent or smaller signed integer type: char, byte, short, int,    |
+ |int32, int64               |long.                                                                 |
+ +---------------------------+----------------------------------------------------------------------+
+ |uint, uint8, uint16,       |Equivalent or smaller unsigned integer type: char, ubyte, ushort,     |
+ |uint32, uint64 types       |uint, ulong                                                           |
+ +---------------------------+----------------------------------------------------------------------+
+ |float32, float64           |Equivalent or smaller float or double.                                |
+ +---------------------------+----------------------------------------------------------------------+
+ |string, []byte             |string, symbol or binary.                                             |
+ +---------------------------+----------------------------------------------------------------------+
+ |map[K]T                    |map, provided all keys and values can unmarshal to types K, T         |
+ +---------------------------+----------------------------------------------------------------------+
+ |Map|map, any AMQP map                                                     |
+ +---------------------------+----------------------------------------------------------------------+
+ |interface{}                |Any AMQP value can be unmarshaled to an interface{} as follows:       |
+ |                           +------------------------+---------------------------------------------+
+ |                           |AMQP Type               |Go Type in interface{}                       |
+ |                           +========================+=============================================+
+ |                           |bool                    |bool                                         |
+ |                           +------------------------+---------------------------------------------+
+ |                           |char                    |unint8                                       |
+ |                           +------------------------+---------------------------------------------+
+ |                           |byte,short,int,long     |int8,int16,int32,int64                       |
+ |                           +------------------------+---------------------------------------------+
+ |                           |ubyte,ushort,uint,ulong |uint8,uint16,uint32,uint64                   |
+ |                           +------------------------+---------------------------------------------+
+ |                           |float, double           |float32, float64                             |
+ |                           +------------------------+---------------------------------------------+
+ |                           |string, symbol          |string                                       |
+ |                           +------------------------+---------------------------------------------+
+ |                           |binary                  |byte[]                                       |
+ |                           +------------------------+---------------------------------------------+
+ |                           |map                     |Map                  |
+ +---------------------------+------------------------+---------------------------------------------+
+
+The following Go types cannot be unmarshaled: complex64/128, uintptr, function, interface, channel.
+
+ODO types
+
+AMQP: null, timestamp, decimal32/64/128, uuid, described, array, list.
+
+Go: array, slice, struct.
 
-Go types that can be unmarshalled from AMQP types
-
-bool from AMQP bool
-
-int, int8, int16, int32, int64 from equivalent or smaller AMQP signed integer type or char
-
-uint, uint8, uint16, uint32, uint64 types from equivalent or smaller AMQP unsigned integer type or char
-
-float32, float64 from equivalent or smaller AMQP float type.
-
-string, []byte from AMQP string, symbol or binary.
-
-TODO types
-
-AMQP from AMQP null, char, timestamp, decimal32/64/128, uuid, described, array, list, map
-
-Go: array, slice, struct, map, reflect/Value
-
-Go types that cannot be unmarshalled
-
-complex64/128, uintptr, function, interface, channel
 */
 func Unmarshal(bytes []byte, v interface{}) (n int, err error) {
-	defer func() {
-		if x := recover(); x != nil {
-			err = errorf("%v", x)
-		}
-	}()
+	defer doRecover(&err)
 	data := C.pn_data(0)
 	defer C.pn_data_free(data)
 	n = unmarshal(data, bytes, v)
@@ -152,15 +172,14 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 	if n == 0 {
 		return 0
 	}
-	pnType := C.pn_data_type(data)
+	get(data, v)
+	return
+}
 
-	badUnmarshal := func() error {
-		notPtr := ""
-		if reflect.TypeOf(v).Kind() != reflect.Ptr {
-			notPtr = ", target is not a pointer"
-		}
-		return errorf("cannot unmarshal AMQP %s to %s%s", getPnType(pnType), reflect.TypeOf(v), notPtr)
-	}
+// Get value v from data
+func get(data *C.pn_data_t, v interface{}) {
+
+	pnType := C.pn_data_type(data)
 
 	switch v := v.(type) {
 	case *bool:
@@ -168,7 +187,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_BOOL:
 			*v = bool(C.pn_data_get_bool(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 	case *int8:
 		switch pnType {
@@ -177,7 +196,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_BYTE:
 			*v = int8(C.pn_data_get_byte(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 	case *uint8:
 		switch pnType {
@@ -186,7 +205,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_UBYTE:
 			*v = uint8(C.pn_data_get_ubyte(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 	case *int16:
 		switch pnType {
@@ -197,7 +216,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_SHORT:
 			*v = int16(C.pn_data_get_short(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 	case *uint16:
 		switch pnType {
@@ -208,7 +227,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_USHORT:
 			*v = uint16(C.pn_data_get_ushort(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 	case *int32:
 		switch pnType {
@@ -221,7 +240,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_INT:
 			*v = int32(C.pn_data_get_int(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 	case *uint32:
 		switch pnType {
@@ -234,7 +253,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_UINT:
 			*v = uint32(C.pn_data_get_uint(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *int64:
@@ -250,7 +269,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_LONG:
 			*v = int64(C.pn_data_get_long(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *uint64:
@@ -264,7 +283,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_ULONG:
 			*v = uint64(C.pn_data_get_ulong(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *int:
@@ -281,10 +300,10 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 			if unsafe.Sizeof(0) == 8 {
 				*v = int(C.pn_data_get_long(data))
 			} else {
-				panic(badUnmarshal())
+				panic(newBadUnmarshal(pnType, v))
 			}
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *uint:
@@ -301,10 +320,10 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 			if unsafe.Sizeof(0) == 8 {
 				*v = uint(C.pn_data_get_ulong(data))
 			} else {
-				panic(badUnmarshal())
+				panic(newBadUnmarshal(pnType, v))
 			}
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *float32:
@@ -312,7 +331,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_FLOAT:
 			*v = float32(C.pn_data_get_float(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *float64:
@@ -322,7 +341,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_DOUBLE:
 			*v = float64(C.pn_data_get_double(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *string:
@@ -334,7 +353,7 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_BINARY:
 			*v = goString(C.pn_data_get_binary(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
 	case *[]byte:
@@ -346,47 +365,24 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 		case C.PN_BINARY:
 			*v = goBytes(C.pn_data_get_binary(data))
 		default:
-			panic(badUnmarshal())
+			panic(newBadUnmarshal(pnType, v))
 		}
 
-	case *reflect.Value:
-		switch pnType {
-		case C.PN_BOOL:
-			*v = reflect.ValueOf(bool(C.pn_data_get_bool(data)))
-		case C.PN_UBYTE:
-			*v = reflect.ValueOf(uint8(C.pn_data_get_ubyte(data)))
-		case C.PN_BYTE:
-			*v = reflect.ValueOf(int8(C.pn_data_get_byte(data)))
-		case C.PN_USHORT:
-			*v = reflect.ValueOf(uint16(C.pn_data_get_ushort(data)))
-		case C.PN_SHORT:
-			*v = reflect.ValueOf(int16(C.pn_data_get_short(data)))
-		case C.PN_UINT:
-			*v = reflect.ValueOf(uint32(C.pn_data_get_uint(data)))
-		case C.PN_INT:
-			*v = reflect.ValueOf(int32(C.pn_data_get_int(data)))
-		case C.PN_CHAR:
-			*v = reflect.ValueOf(uint8(C.pn_data_get_char(data)))
-		case C.PN_ULONG:
-			*v = reflect.ValueOf(uint64(C.pn_data_get_ulong(data)))
-		case C.PN_LONG:
-			*v = reflect.ValueOf(int64(C.pn_data_get_long(data)))
-		case C.PN_FLOAT:
-			*v = reflect.ValueOf(float32(C.pn_data_get_float(data)))
-		case C.PN_DOUBLE:
-			*v = reflect.ValueOf(float64(C.pn_data_get_double(data)))
-		case C.PN_BINARY:
-			*v = valueOfBytes(C.pn_data_get_binary(data))
-		case C.PN_STRING:
-			*v = valueOfString(C.pn_data_get_string(data))
-		case C.PN_SYMBOL:
-			*v = valueOfString(C.pn_data_get_symbol(data))
-		default:
-			panic(badUnmarshal())
-		}
+	case *interface{}:
+		getInterface(data, v)
 
 	default:
-		panic(badUnmarshal())
+		if reflect.TypeOf(v).Kind() != reflect.Ptr {
+			panic(newBadUnmarshal(pnType, v))
+		}
+		switch reflect.TypeOf(v).Elem().Kind() {
+		case reflect.Map:
+			getMap(data, v)
+		case reflect.Slice:
+			getList(data, v)
+		default:
+			panic(newBadUnmarshal(pnType, v))
+		}
 	}
 	err := pnDataError(data)
 	if err != "" {
@@ -395,6 +391,98 @@ func unmarshal(data *C.pn_data_t, bytes []byte, v interface{}) (n int) {
 	return
 }
 
+// Getting into an interface is driven completely by the AMQP type, since the interface{}
+// target is type-neutral.
+func getInterface(data *C.pn_data_t, v *interface{}) {
+	pnType := C.pn_data_type(data)
+	switch pnType {
+	case C.PN_BOOL:
+		*v = bool(C.pn_data_get_bool(data))
+	case C.PN_UBYTE:
+		*v = uint8(C.pn_data_get_ubyte(data))
+	case C.PN_BYTE:
+		*v = int8(C.pn_data_get_byte(data))
+	case C.PN_USHORT:
+		*v = uint16(C.pn_data_get_ushort(data))
+	case C.PN_SHORT:
+		*v = int16(C.pn_data_get_short(data))
+	case C.PN_UINT:
+		*v = uint32(C.pn_data_get_uint(data))
+	case C.PN_INT:
+		*v = int32(C.pn_data_get_int(data))
+	case C.PN_CHAR:
+		*v = uint8(C.pn_data_get_char(data))
+	case C.PN_ULONG:
+		*v = uint64(C.pn_data_get_ulong(data))
+	case C.PN_LONG:
+		*v = int64(C.pn_data_get_long(data))
+	case C.PN_FLOAT:
+		*v = float32(C.pn_data_get_float(data))
+	case C.PN_DOUBLE:
+		*v = float64(C.pn_data_get_double(data))
+	case C.PN_BINARY:
+		*v = goBytes(C.pn_data_get_binary(data))
+	case C.PN_STRING:
+		*v = goString(C.pn_data_get_string(data))
+	case C.PN_SYMBOL:
+		*v = goString(C.pn_data_get_symbol(data))
+	case C.PN_MAP:
+		m := make(Map)
+		get(data, &m)
+		*v = m // FIXME aconway 2015-03-13: avoid the copy?
+	case C.PN_LIST:
+		l := make(List, 0)
+		get(data, &l)
+		*v = l
+	default:
+		panic(newBadUnmarshal(pnType, v))
+	}
+}
+
+func getMap(data *C.pn_data_t, v interface{}) {
+	pnType := C.pn_data_type(data)
+	if pnType != C.PN_MAP {
+		panic(newBadUnmarshal(pnType, v))
+	}
+	mapValue := reflect.ValueOf(v).Elem()
+	mapValue.Set(reflect.MakeMap(mapValue.Type())) // Clear the map
+	count := int(C.pn_data_get_map(data))
+	if bool(C.pn_data_enter(data)) {
+		for i := 0; i < count/2; i++ {
+			if bool(C.pn_data_next(data)) {
+				key := reflect.New(mapValue.Type().Key())
+				get(data, key.Interface())
+				if bool(C.pn_data_next(data)) {
+					val := reflect.New(mapValue.Type().Elem())
+					get(data, val.Interface())
+					mapValue.SetMapIndex(key.Elem(), val.Elem())
+				}
+			}
+		}
+		C.pn_data_exit(data)
+	}
+}
+
+func getList(data *C.pn_data_t, v interface{}) {
+	pnType := C.pn_data_type(data)
+	if pnType != C.PN_LIST {
+		panic(newBadUnmarshal(pnType, v))
+	}
+	count := int(C.pn_data_get_list(data))
+	listValue := reflect.MakeSlice(reflect.TypeOf(v).Elem(), count, count)
+	if bool(C.pn_data_enter(data)) {
+		for i := 0; i < count; i++ {
+			if bool(C.pn_data_next(data)) {
+				val := reflect.New(listValue.Type().Elem())
+				get(data, val.Interface())
+				listValue.Index(i).Set(val.Elem())
+			}
+		}
+		C.pn_data_exit(data)
+	}
+	reflect.ValueOf(v).Elem().Set(listValue)
+}
+
 // decode from bytes.
 // Return bytes decoded or 0 if we could not decode a complete object.
 //
@@ -412,20 +500,6 @@ func decode(data *C.pn_data_t, bytes []byte) int {
 	return n
 }
 
-func valueOfBytes(bytes C.pn_bytes_t) reflect.Value {
-	if bytes.start == nil || bytes.size == 0 {
-		return reflect.ValueOf([]byte{})
-	}
-	return reflect.ValueOf(C.GoBytes(unsafe.Pointer(bytes.start), C.int(bytes.size)))
-}
-
-func valueOfString(bytes C.pn_bytes_t) reflect.Value {
-	if bytes.start == nil || bytes.size == 0 {
-		return reflect.ValueOf("")
-	}
-	return reflect.ValueOf(C.GoStringN(bytes.start, C.int(bytes.size)))
-}
-
 func goBytes(cBytes C.pn_bytes_t) (bytes []byte) {
 	if cBytes.start != nil {
 		bytes = C.GoBytes(unsafe.Pointer(cBytes.start), C.int(cBytes.size))


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org