PROTON-1456: Support for AMQP described types Support AMQP described types, required for compliant AMQP 1.0 filters.
Added some extra encoding/decoding tests in types_test.go Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/4f724ace Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/4f724ace Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/4f724ace Branch: refs/heads/PROTON-1488 Commit: 4f724ace01f942c69e650c4e7c513c327d76cfbc Parents: 110f851 Author: Alan Conway <[email protected]> Authored: Tue May 30 16:02:51 2017 -0400 Committer: Alan Conway <[email protected]> Committed: Wed May 31 17:18:29 2017 -0400 ---------------------------------------------------------------------- proton-c/bindings/go/CMakeLists.txt | 1 + .../go/src/qpid.apache.org/amqp/interop_test.go | 8 - .../go/src/qpid.apache.org/amqp/marshal.go | 19 +- .../go/src/qpid.apache.org/amqp/types.go | 11 +- .../go/src/qpid.apache.org/amqp/types_test.go | 163 ++++++++++++++++ .../go/src/qpid.apache.org/amqp/unmarshal.go | 185 ++++++++++++------- 6 files changed, 306 insertions(+), 81 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4f724ace/proton-c/bindings/go/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/proton-c/bindings/go/CMakeLists.txt b/proton-c/bindings/go/CMakeLists.txt index d0ffe9b..7f814b9 100644 --- a/proton-c/bindings/go/CMakeLists.txt +++ b/proton-c/bindings/go/CMakeLists.txt @@ -23,6 +23,7 @@ message(STATUS "Found Go: ${GO_EXE} (${go_ver})") set(GO_BUILD_FLAGS "" CACHE STRING "Flags for 'go build'") set(GO_TEST_FLAGS "-v" CACHE STRING "Flags for 'go test'") +set(GO_VET_FLAGS "-v" CACHE STRING "Flags for 'go test'") # Flags that differ for golang go and gcc go. if (go_ver MATCHES "gccgo") http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4f724ace/proton-c/bindings/go/src/qpid.apache.org/amqp/interop_test.go ---------------------------------------------------------------------- diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/interop_test.go b/proton-c/bindings/go/src/qpid.apache.org/amqp/interop_test.go index b3e27bc..a5fb92e 100644 --- a/proton-c/bindings/go/src/qpid.apache.org/amqp/interop_test.go +++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/interop_test.go @@ -24,7 +24,6 @@ package amqp import ( "bytes" - "fmt" "io" "io/ioutil" "os" @@ -33,13 +32,6 @@ import ( "testing" ) -func checkEqual(want interface{}, got interface{}) error { - if !reflect.DeepEqual(want, got) { - return fmt.Errorf("%#v != %#v", want, got) - } - return nil -} - func getReader(t *testing.T, name string) (r io.Reader) { dir := os.Getenv("PN_INTEROP_DIR") if dir == "" { http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4f724ace/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal.go ---------------------------------------------------------------------- diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal.go b/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal.go index a0a732e..04ecf04 100644 --- a/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal.go +++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal.go @@ -87,17 +87,16 @@ Go types are encoded as follows +-------------------------------------+--------------------------------------------+ |List |list, may have mixed types values | +-------------------------------------+--------------------------------------------+ + |Described |described type | + +-------------------------------------+--------------------------------------------+ -The following Go types cannot be marshaled: uintptr, function, interface, channel - -TODO - -Go types: array, slice, struct, complex64/128. +The following Go types cannot be marshaled: uintptr, function, channel, array (use slice), struct -AMQP types: decimal32/64/128, char, timestamp, uuid, array, multi-section message bodies. +TODO: Not yet implemented: -Described types. +Go types: struct, complex64/128. +AMQP types: decimal32/64/128, char, timestamp, uuid, array. */ func Marshal(v interface{}, buffer []byte) (outbuf []byte, err error) { defer func() { @@ -204,6 +203,12 @@ func marshal(v interface{}, data *C.pn_data_t) { marshal(val, data) } C.pn_data_exit(data) + case Described: + C.pn_data_put_described(data) + C.pn_data_enter(data) + marshal(v.Descriptor, data) + marshal(v.Value, data) + C.pn_data_exit(data) case Key: marshal(v.Get(), data) default: http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4f724ace/proton-c/bindings/go/src/qpid.apache.org/amqp/types.go ---------------------------------------------------------------------- diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/types.go b/proton-c/bindings/go/src/qpid.apache.org/amqp/types.go index 76f223f..6e2ebc8 100644 --- a/proton-c/bindings/go/src/qpid.apache.org/amqp/types.go +++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/types.go @@ -83,7 +83,7 @@ func (t C.pn_type_t) String() string { case C.PN_MAP: return "map" default: - return "no-data" + return fmt.Sprintf("<bad-type %v>", int(t)) } } @@ -210,3 +210,12 @@ func StringKey(v string) Key { return Key{Symbol(v)} } func (k Key) Get() interface{} { return k.value } func (k Key) String() string { return fmt.Sprintf("%v", k.Get()) } + +// Described represents an AMQP described type, which is really +// just a pair of AMQP values - the first is treated as a "descriptor", +// and is normally a string or ulong providing information about the type. +// The second is the "value" and can be any AMQP value. +type Described struct { + Descriptor interface{} + Value interface{} +} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4f724ace/proton-c/bindings/go/src/qpid.apache.org/amqp/types_test.go ---------------------------------------------------------------------- diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/types_test.go b/proton-c/bindings/go/src/qpid.apache.org/amqp/types_test.go index b9c0596..096d27f 100644 --- a/proton-c/bindings/go/src/qpid.apache.org/amqp/types_test.go +++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/types_test.go @@ -21,8 +21,28 @@ package amqp import ( "fmt" + "reflect" + "testing" ) +func checkEqual(want interface{}, got interface{}) error { + if !reflect.DeepEqual(want, got) { + return fmt.Errorf("%#v != %#v", want, got) + } + return nil +} + +func checkUnmarshal(marshalled []byte, v interface{}) error { + got, err := Unmarshal(marshalled, v) + if err != nil { + return err + } + if got != len(marshalled) { + return fmt.Errorf("Wanted to Unmarshal %v bytes, got %v", len(marshalled), got) + } + return nil +} + func ExampleKey() { var k Key = SymbolKey(Symbol("foo")) fmt.Println(k.Get().(Symbol)) @@ -32,3 +52,146 @@ func ExampleKey() { // foo // 42 } + +// Values that are unchanged by a marshal/unmarshal round-trip from interface{} +// to interface{} +var rtValues = []interface{}{ + true, + int8(-8), int16(-16), int32(-32), int64(-64), + uint8(8), uint16(16), uint32(32), uint64(64), + float32(0.32), float64(0.64), + "string", Binary("Binary"), Symbol("symbol"), + nil, + Map{"V": "X"}, + List{"V", int32(1)}, + Described{"D", "V"}, +} + +// Go values that unmarshal as an equivalent value but a different type +// if unmarshalled to interface{}. +var oddValues = []interface{}{ + int(-99), uint(99), // [u]int32|64 + []byte("byte"), // amqp.Binary + map[string]int{"str": 99}, // amqp.Map + []string{"a", "b"}, // amqp.List +} + +var allValues = append(rtValues, oddValues...) + +// %v formatted representation of allValues +var vstrings = []string{ + // for rtValues + "true", + "-8", "-16", "-32", "-64", + "8", "16", "32", "64", + "0.32", "0.64", + "string", "Binary", "symbol", + "<nil>", + "map[V:X]", + "[V 1]", + "{D V}", + // for oddValues + "-99", "99", + "[98 121 116 101]", /*"byte"*/ + "map[str:99]", + "[a b]", +} + +// Round-trip encoding test +func TestTypesRoundTrip(t *testing.T) { + for _, x := range rtValues { + marshalled, err := Marshal(x, nil) + if err != nil { + t.Error(err) + } + var v interface{} + if err := checkUnmarshal(marshalled, &v); err != nil { + t.Error(err) + } + if err := checkEqual(v, x); err != nil { + t.Error(t, err) + } + } +} + +// Round trip from T to T where T is the type of the value. +func TestTypesRoundTripAll(t *testing.T) { + for _, x := range allValues { + marshalled, err := Marshal(x, nil) + if err != nil { + t.Error(err) + } + if x == nil { // We can't create an instance of nil to unmarshal to. + continue + } + vp := reflect.New(reflect.TypeOf(x)) // v points to a Zero of the same type as x + if err := checkUnmarshal(marshalled, vp.Interface()); err != nil { + t.Error(err) + } + v := vp.Elem().Interface() + if err := checkEqual(v, x); err != nil { + t.Error(err) + } + } +} + +func TestTypesPrint(t *testing.T) { + // Default %v representations of rtValues and oddValues + for i, x := range allValues { + if s := fmt.Sprintf("%v", x); vstrings[i] != s { + t.Errorf("printing %T: want %v, got %v", x, vstrings[i], s) + } + } +} + +func TestDescribed(t *testing.T) { + want := Described{"D", "V"} + marshalled, _ := Marshal(want, nil) + + // Unmarshal to Described type + var d Described + if err := checkUnmarshal(marshalled, &d); err != nil { + t.Error(err) + } + if err := checkEqual(want, d); err != nil { + t.Error(err) + } + + // Unmarshal to interface{} + var i interface{} + if err := checkUnmarshal(marshalled, &i); err != nil { + t.Error(err) + } + if _, ok := i.(Described); !ok { + t.Errorf("Expected Described, got %T(%v)", i, i) + } + if err := checkEqual(want, i); err != nil { + t.Error(err) + } + + // Unmarshal value only (drop descriptor) to the value type + var s string + if err := checkUnmarshal(marshalled, &s); err != nil { + t.Error(err) + } + if err := checkEqual(want.Value, s); err != nil { + t.Error(err) + } + + // Nested described types + want = Described{Described{int64(123), true}, "foo"} + marshalled, _ = Marshal(want, nil) + if err := checkUnmarshal(marshalled, &d); err != nil { + t.Error(err) + } + if err := checkEqual(want, d); err != nil { + t.Error(err) + } + // Nested to interface + if err := checkUnmarshal(marshalled, &i); err != nil { + t.Error(err) + } + if err := checkEqual(want, i); err != nil { + t.Error(err) + } +} http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4f724ace/proton-c/bindings/go/src/qpid.apache.org/amqp/unmarshal.go ---------------------------------------------------------------------- diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/unmarshal.go b/proton-c/bindings/go/src/qpid.apache.org/amqp/unmarshal.go index 96b8e05..b49727e 100644 --- a/proton-c/bindings/go/src/qpid.apache.org/amqp/unmarshal.go +++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/unmarshal.go @@ -27,6 +27,7 @@ import ( "fmt" "io" "reflect" + "strings" "unsafe" ) @@ -44,16 +45,23 @@ type UnmarshalError struct { func (e UnmarshalError) Error() string { return e.s } -func newUnmarshalError(pnType C.pn_type_t, v interface{}) *UnmarshalError { +func newUnmarshalErrorMsg(pnType C.pn_type_t, v interface{}, msg string) *UnmarshalError { + if len(msg) > 0 && !strings.HasPrefix(msg, ":") { + msg = ": " + msg + } e := &UnmarshalError{AMQPType: C.pn_type_t(pnType).String(), GoType: reflect.TypeOf(v)} if e.GoType.Kind() != reflect.Ptr { - e.s = fmt.Sprintf("cannot unmarshal to type %s, not a pointer", e.GoType) + e.s = fmt.Sprintf("cannot unmarshal to type %s, not a pointer%s", e.GoType, msg) } else { - e.s = fmt.Sprintf("cannot unmarshal AMQP %s to %s", e.AMQPType, e.GoType) + e.s = fmt.Sprintf("cannot unmarshal AMQP %s to %s%s", e.AMQPType, e.GoType, msg) } return e } +func newUnmarshalError(pnType C.pn_type_t, v interface{}) *UnmarshalError { + return newUnmarshalErrorMsg(pnType, v, "") +} + func newUnmarshalErrorData(data *C.pn_data_t, v interface{}) *UnmarshalError { err := PnError(C.pn_data_error(data)) if err == nil { @@ -130,68 +138,73 @@ func (d *Decoder) Decode(v interface{}) (err error) { } /* -Unmarshal decodes AMQP-encoded bytes and stores the result in the value pointed to by v. +Unmarshal decodes AMQP-encoded bytes and stores the result in the Go 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: byte, short, int, long. | - |int32, int64 | | - +---------------------------+----------------------------------------------------------------------+ - |uint, uint8, uint16, |Equivalent or smaller unsigned integer type: ubyte, ushort, uint, | - |uint32, uint64 types |ulong | - +---------------------------+----------------------------------------------------------------------+ - |float32, float64 |Equivalent or smaller float or double. | - +---------------------------+----------------------------------------------------------------------+ - |string, []byte |string, symbol or binary. | - +---------------------------+----------------------------------------------------------------------+ - |Symbol |symbol | - +---------------------------+----------------------------------------------------------------------+ - |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 | - | +------------------------+---------------------------------------------+ - | |byte,short,int,long |int8,int16,int32,int64 | - | +------------------------+---------------------------------------------+ - | |ubyte,ushort,uint,ulong |uint8,uint16,uint32,uint64 | - | +------------------------+---------------------------------------------+ - | |float, double |float32, float64 | - | +------------------------+---------------------------------------------+ - | |string |string | - | +------------------------+---------------------------------------------+ - | |symbol |Symbol | - | +------------------------+---------------------------------------------+ - | |binary |Binary | - | +------------------------+---------------------------------------------+ - | |nulll |nil | - | +------------------------+---------------------------------------------+ - | |map |Map | - | +------------------------+---------------------------------------------+ - | |list |List | - +---------------------------+------------------------+---------------------------------------------+ - |Key |symbol, ulong | - +---------------------------+----------------------------------------------------------------------+ - -The following Go types cannot be unmarshaled: uintptr, function, interface, channel. - -TODO - -Go types: array, struct. - -AMQP types: decimal32/64/128, char (round trip), timestamp, uuid, array, multi-section message bodies. - -AMQP maps with mixed/unhashable key types need an alternate representation. - -Described types. + +------------------------+-------------------------------------------------+ + |To Go types |From AMQP types | + +========================+=================================================+ + |bool |bool | + +------------------------+-------------------------------------------------+ + |int, int8, int16, int32,|Equivalent or smaller signed integer type: byte, | + |int64 |short, int, long. | + +------------------------+-------------------------------------------------+ + |uint, uint8, uint16, |Equivalent or smaller unsigned integer type: | + |uint32, uint64 |ubyte, ushort, uint, ulong | + +------------------------+-------------------------------------------------+ + |float32, float64 |Equivalent or smaller float or double. | + +------------------------+-------------------------------------------------+ + |string, []byte |string, symbol or binary. | + +------------------------+-------------------------------------------------+ + |Symbol |symbol | + +------------------------+-------------------------------------------------+ + |map[K]T |map, provided all keys and values can unmarshal | + | |to types K,T | + +------------------------+-------------------------------------------------+ + |Map |map, any AMQP map | + +------------------------+-------------------------------------------------+ + |Described |described type | + +------------------------+-------------------------------------------------+ + +An AMQP described type can unmarshal into the corresponding plain type, discarding the descriptor. +For example an AMQP described string can unmarshal into a plain go string. +Unmarshal into the Described type preserves the descriptor. + +Any AMQP type can unmarshal to an interface{}, the Go type used to unmarshal is chosen from the AMQP type as follows + + +------------------------+-------------------------------------------------+ + |AMQP Type |Go Type in interface{} | + +========================+=================================================+ + |bool |bool | + +------------------------+-------------------------------------------------+ + |byte,short,int,long |int8,int16,int32,int64 | + +------------------------+-------------------------------------------------+ + |ubyte,ushort,uint,ulong |uint8,uint16,uint32,uint64 | + +------------------------+-------------------------------------------------+ + |float, double |float32, float64 | + +------------------------+-------------------------------------------------+ + |string |string | + +------------------------+-------------------------------------------------+ + |symbol |Symbol | + +------------------------+-------------------------------------------------+ + |binary |Binary | + +------------------------+-------------------------------------------------+ + |null |nil | + +------------------------+-------------------------------------------------+ + |map |Map | + +------------------------+-------------------------------------------------+ + |list |List | + +------------------------+-------------------------------------------------+ + |described type |Described | + +--------------------------------------------------------------------------+ + +The following Go types cannot be unmarshaled: uintptr, function, interface, channel, array (use slice), struct + +TODO: Not yet implemented: + +AMQP types: decimal32/64/128, char (round trip), timestamp, uuid. + +AMQP maps with mixed key types, or key types that are not legal Go map keys. */ func Unmarshal(bytes []byte, v interface{}) (n int, err error) { defer recoverUnmarshal(&err) @@ -227,6 +240,15 @@ func (d *Decoder) more() error { // Unmarshal from data into value pointed at by v. func unmarshal(v interface{}, data *C.pn_data_t) { pnType := C.pn_data_type(data) + + // Check for PN_DESCRIBED first, as described types can unmarshal into any of the Go types. + // Interfaces are handled in the switch below, even for described types. + if _, isInterface := v.(*interface{}); !isInterface && bool(C.pn_data_is_described(data)) { + getDescribed(data, v) + return + } + + // Unmarshal based on the target type switch v := v.(type) { case *bool: switch pnType { @@ -440,7 +462,7 @@ func unmarshal(v interface{}, data *C.pn_data_t) { panic(newUnmarshalError(pnType, v)) } - default: + default: // This is not one of the fixed well-known types, reflect for map and slice types if reflect.TypeOf(v).Kind() != reflect.Ptr { panic(newUnmarshalError(pnType, v)) } @@ -508,8 +530,18 @@ func getInterface(data *C.pn_data_t, v *interface{}) { l := make(List, 0) unmarshal(&l, data) *v = l - default: // No data (-1 or NULL) + case C.PN_DESCRIBED: + d := Described{} + unmarshal(&d, data) + *v = d + case C.PN_NULL: *v = nil + case C.PN_INVALID: + // Allow decoding from an empty data object to an interface, treat it like NULL. + // This happens when optional values or properties are omitted from a message. + *v = nil + default: // Don't know how to handle this + panic(newUnmarshalError(pnType, v)) } } @@ -558,6 +590,29 @@ func getList(data *C.pn_data_t, v interface{}) { reflect.ValueOf(v).Elem().Set(listValue) } +func getDescribed(data *C.pn_data_t, v interface{}) { + d, _ := v.(*Described) + pnType := C.pn_data_type(data) + if bool(C.pn_data_enter(data)) { + defer C.pn_data_exit(data) + if bool(C.pn_data_next(data)) { + if d != nil { + unmarshal(&d.Descriptor, data) + } + if bool(C.pn_data_next(data)) { + if d != nil { + unmarshal(&d.Value, data) + } else { + unmarshal(v, data) + } + return + } + } + } + // The pn_data cursor didn't move as expected + panic(newUnmarshalErrorMsg(pnType, v, "bad described value encoding")) +} + // decode from bytes. // Return bytes decoded or 0 if we could not decode a complete object. // --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
