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]

Reply via email to