PROTON-1450: go binding amqp.Key and message map types.

Add amqp.Key type for message maps with keys that can be ulong or symbol.

Add type-safe methods to deal with amqp.Message maps, old methods remain for
compatibility but are deprecated.


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

Branch: refs/heads/PROTON-1488
Commit: 110f851add21f4f79de08a0af55d93226593c26c
Parents: 5f8738f
Author: Alan Conway <[email protected]>
Authored: Thu Apr 27 17:16:19 2017 -0400
Committer: Alan Conway <[email protected]>
Committed: Wed May 31 17:17:50 2017 -0400

----------------------------------------------------------------------
 .../go/src/qpid.apache.org/amqp/marshal.go      |   2 +
 .../go/src/qpid.apache.org/amqp/marshal_test.go |  90 ++++++++++++++++
 .../go/src/qpid.apache.org/amqp/message.go      | 106 ++++++++++++++-----
 .../go/src/qpid.apache.org/amqp/message_test.go |  57 ++++++++--
 .../go/src/qpid.apache.org/amqp/types.go        |  18 ++++
 .../go/src/qpid.apache.org/amqp/types_test.go   |  34 ++++++
 .../go/src/qpid.apache.org/amqp/unmarshal.go    |   9 ++
 7 files changed, 285 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/110f851a/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 b6adf90..a0a732e 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
@@ -204,6 +204,8 @@ func marshal(v interface{}, data *C.pn_data_t) {
                        marshal(val, data)
                }
                C.pn_data_exit(data)
+       case Key:
+               marshal(v.Get(), data)
        default:
                switch reflect.TypeOf(v).Kind() {
                case reflect.Map:

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/110f851a/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal_test.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal_test.go 
b/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal_test.go
new file mode 100644
index 0000000..2eda33c
--- /dev/null
+++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/marshal_test.go
@@ -0,0 +1,90 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package amqp
+
+import (
+       "testing"
+)
+
+func TestSymbolKey(t *testing.T) {
+       bytes, err := Marshal(SymbolKey("foo"), nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       var k Key
+       if _, err := Unmarshal(bytes, &k); err != nil {
+               t.Error(err)
+       }
+       if err := checkEqual("foo", string(k.Get().(Symbol))); err != nil {
+               t.Error(err)
+       }
+       var sym Symbol
+       if _, err := Unmarshal(bytes, &sym); err != nil {
+               t.Error(err)
+       }
+       if err := checkEqual("foo", sym.String()); err != nil {
+               t.Error(err)
+       }
+
+}
+
+func TestStringKey(t *testing.T) {
+       bytes, err := Marshal(StringKey("foo"), nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       var k Key
+       if _, err := Unmarshal(bytes, &k); err != nil {
+               t.Error(err)
+       }
+       if err := checkEqual("foo", string(k.Get().(Symbol))); err != nil {
+               t.Error(err)
+       }
+       var s string
+       if _, err := Unmarshal(bytes, &s); err != nil {
+               t.Error(err)
+       }
+       if err := checkEqual("foo", s); err != nil {
+               t.Error(err)
+       }
+
+}
+
+func TestIntKey(t *testing.T) {
+       bytes, err := Marshal(IntKey(12345), nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       var k Key
+       if _, err := Unmarshal(bytes, &k); err != nil {
+               t.Error(err)
+       }
+       if 12345 != k.Get().(uint64) {
+               t.Errorf("(%T)%v != (%T)%v", 12345, k.Get().(uint64))
+       }
+       var n uint64
+       if _, err := Unmarshal(bytes, &n); err != nil {
+               t.Error(err)
+       }
+       if 12345 != n {
+               t.Errorf("%v != %v", 12345, k.Get().(uint64))
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/110f851a/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go 
b/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
index 753682e..9f4d7d1 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/message.go
@@ -117,17 +117,20 @@ type Message interface {
        ReplyToGroupId() string
        SetReplyToGroupId(string)
 
-       // Instructions - AMQP delivery instructions.
-       Instructions() map[string]interface{}
-       SetInstructions(v map[string]interface{})
+       // Property map set by the application to be carried with the message.
+       // Values must be simple types (not maps, lists or sequences)
+       ApplicationProperties() map[Key]interface{}
+       SetApplicationProperties(map[Key]interface{})
 
-       // Annotations - AMQP annotations.
-       Annotations() map[string]interface{}
-       SetAnnotations(v map[string]interface{})
+       // Per-delivery annotations to provide delivery instructions.
+       // May be added or removed by intermediaries during delivery.
+       DeliveryAnnotations() map[Key]interface{}
+       SetDeliveryAnnotations(map[Key]interface{})
 
-       // Properties - Application properties.
-       Properties() map[string]interface{}
-       SetProperties(v map[string]interface{})
+       // Message annotations added as part of the bare message at creation, 
usually
+       // by an AMQP library. See ApplicationProperties() for adding 
application data.
+       MessageAnnotations() map[Key]interface{}
+       SetMessageAnnotations(map[Key]interface{})
 
        // Inferred indicates how the message content
        // is encoded into AMQP sections. If inferred is true then binary and
@@ -160,6 +163,18 @@ type Message interface {
 
        // Copy the contents of another message to this one.
        Copy(m Message) error
+
+       // Deprecated: use DeliveryAnnotations() for a more type-safe interface
+       Instructions() map[string]interface{}
+       SetInstructions(v map[string]interface{})
+
+       // Deprecated: use MessageAnnotations() for a more type-safe interface
+       Annotations() map[string]interface{}
+       SetAnnotations(v map[string]interface{})
+
+       // Deprecated: use ApplicationProperties() for a more type-safe 
interface
+       Properties() map[string]interface{}
+       SetProperties(v map[string]interface{})
 }
 
 type message struct{ pn *C.pn_message_t }
@@ -203,13 +218,6 @@ func rewindGet(data *C.pn_data_t) (v interface{}) {
        return v
 }
 
-func rewindMap(data *C.pn_data_t) (v map[string]interface{}) {
-       C.pn_data_rewind(data)
-       C.pn_data_next(data)
-       unmarshal(&v, data)
-       return v
-}
-
 func (m *message) Inferred() bool  { return 
bool(C.pn_message_is_inferred(m.pn)) }
 func (m *message) Durable() bool   { return 
bool(C.pn_message_is_durable(m.pn)) }
 func (m *message) Priority() uint8 { return 
uint8(C.pn_message_get_priority(m.pn)) }
@@ -237,14 +245,21 @@ func (m *message) GroupId() string        { return 
C.GoString(C.pn_message_get_g
 func (m *message) GroupSequence() int32   { return 
int32(C.pn_message_get_group_sequence(m.pn)) }
 func (m *message) ReplyToGroupId() string { return 
C.GoString(C.pn_message_get_reply_to_group_id(m.pn)) }
 
-func (m *message) Instructions() map[string]interface{} {
-       return rewindMap(C.pn_message_instructions(m.pn))
+func getAnnotations(data *C.pn_data_t) (v map[Key]interface{}) {
+       C.pn_data_rewind(data)
+       C.pn_data_next(data)
+       unmarshal(&v, data)
+       return v
 }
-func (m *message) Annotations() map[string]interface{} {
-       return rewindMap(C.pn_message_annotations(m.pn))
+
+func (m *message) DeliveryAnnotations() map[Key]interface{} {
+       return getAnnotations(C.pn_message_instructions(m.pn))
 }
-func (m *message) Properties() map[string]interface{} {
-       return rewindMap(C.pn_message_properties(m.pn))
+func (m *message) MessageAnnotations() map[Key]interface{} {
+       return getAnnotations(C.pn_message_annotations(m.pn))
+}
+func (m *message) ApplicationProperties() map[Key]interface{} {
+       return getAnnotations(C.pn_message_properties(m.pn))
 }
 
 // ==== message set methods
@@ -299,11 +314,15 @@ func (m *message) SetReplyToGroupId(s string) {
        C.msg_set_str(m.pn, C.CString(s), 
C.set_fn(C.pn_message_set_reply_to_group_id))
 }
 
-func (m *message) SetInstructions(v map[string]interface{}) {
+func (m *message) SetDeliveryAnnotations(v map[Key]interface{}) {
        setData(v, C.pn_message_instructions(m.pn))
 }
-func (m *message) SetAnnotations(v map[string]interface{}) { setData(v, 
C.pn_message_annotations(m.pn)) }
-func (m *message) SetProperties(v map[string]interface{})  { setData(v, 
C.pn_message_properties(m.pn)) }
+func (m *message) SetMessageAnnotations(v map[Key]interface{}) {
+       setData(v, C.pn_message_annotations(m.pn))
+}
+func (m *message) SetApplicationProperties(v map[Key]interface{}) {
+       setData(v, C.pn_message_properties(m.pn))
+}
 
 // Marshal/Unmarshal body
 func (m *message) Marshal(v interface{})   { clearMarshal(v, 
C.pn_message_body(m.pn)) }
@@ -346,3 +365,40 @@ func (m *message) Encode(buffer []byte) ([]byte, error) {
 // TODO aconway 2015-09-14: Multi-section messages.
 
 // TODO aconway 2016-09-09: Message.String() use inspect.
+
+// ==== Deprecated functions
+func oldGetAnnotations(data *C.pn_data_t) (v map[string]interface{}) {
+       C.pn_data_rewind(data)
+       C.pn_data_next(data)
+       unmarshal(&v, data)
+       return v
+}
+
+func (m *message) Instructions() map[string]interface{} {
+       return oldGetAnnotations(C.pn_message_instructions(m.pn))
+}
+func (m *message) Annotations() map[string]interface{} {
+       return oldGetAnnotations(C.pn_message_annotations(m.pn))
+}
+func (m *message) Properties() map[string]interface{} {
+       return oldGetAnnotations(C.pn_message_properties(m.pn))
+}
+
+// Convert old string-keyed annotations to a Key map
+func fixAnnotations(old map[string]interface{}) (annotations 
map[Key]interface{}) {
+       annotations = make(map[Key]interface{})
+       for k, v := range old {
+               annotations[StringKey(k)] = v
+       }
+       return
+}
+
+func (m *message) SetInstructions(v map[string]interface{}) {
+       setData(fixAnnotations(v), C.pn_message_instructions(m.pn))
+}
+func (m *message) SetAnnotations(v map[string]interface{}) {
+       setData(fixAnnotations(v), C.pn_message_annotations(m.pn))
+}
+func (m *message) SetProperties(v map[string]interface{}) {
+       setData(fixAnnotations(v), C.pn_message_properties(m.pn))
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/110f851a/proton-c/bindings/go/src/qpid.apache.org/amqp/message_test.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/qpid.apache.org/amqp/message_test.go 
b/proton-c/bindings/go/src/qpid.apache.org/amqp/message_test.go
index 7a6e5a8..3081cc5 100644
--- a/proton-c/bindings/go/src/qpid.apache.org/amqp/message_test.go
+++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/message_test.go
@@ -55,6 +55,11 @@ func TestDefaultMessage(t *testing.T) {
                {m.ReplyToGroupId(), ""},
                {m.MessageId(), nil},
                {m.CorrelationId(), nil},
+               {m.DeliveryAnnotations(), map[Key]interface{}{}},
+               {m.MessageAnnotations(), map[Key]interface{}{}},
+               {m.ApplicationProperties(), map[Key]interface{}{}},
+
+               // Deprecated
                {m.Instructions(), map[string]interface{}{}},
                {m.Annotations(), map[string]interface{}{}},
                {m.Properties(), map[string]interface{}{}},
@@ -86,9 +91,9 @@ func TestMessageRoundTrip(t *testing.T) {
        m.SetReplyToGroupId("replytogroup")
        m.SetMessageId("id")
        m.SetCorrelationId("correlation")
-       m.SetInstructions(map[string]interface{}{"instructions": "foo"})
-       m.SetAnnotations(map[string]interface{}{"annotations": "foo"})
-       m.SetProperties(map[string]interface{}{"int": int32(32), "bool": true, 
"string": "foo"})
+       m.SetDeliveryAnnotations(map[Key]interface{}{SymbolKey("instructions"): 
"foo"})
+       m.SetMessageAnnotations(map[Key]interface{}{SymbolKey("annotations"): 
"bar"})
+       m.SetApplicationProperties(map[Key]interface{}{SymbolKey("int"): 
int32(32), SymbolKey("bool"): true, IntKey(42): "42"})
        m.Marshal("hello")
 
        for _, data := range [][]interface{}{
@@ -107,10 +112,50 @@ func TestMessageRoundTrip(t *testing.T) {
                {m.ReplyToGroupId(), "replytogroup"},
                {m.MessageId(), "id"},
                {m.CorrelationId(), "correlation"},
-               {m.Instructions(), map[string]interface{}{"instructions": 
"foo"}},
-               {m.Annotations(), map[string]interface{}{"annotations": "foo"}},
-               {m.Properties(), map[string]interface{}{"int": int32(32), 
"bool": true, "string": "foo"}},
+
+               {m.DeliveryAnnotations(), 
map[Key]interface{}{SymbolKey("instructions"): "foo"}},
+               {m.MessageAnnotations(), 
map[Key]interface{}{SymbolKey("annotations"): "bar"}},
+               {m.ApplicationProperties(), 
map[Key]interface{}{SymbolKey("int"): int32(32), SymbolKey("bool"): true, 
IntKey(42): "42"}},
                {m.Body(), "hello"},
+
+               // Deprecated
+               {m.Instructions(), map[string]interface{}{"instructions": 
"foo"}},
+               {m.Annotations(), map[string]interface{}{"annotations": "bar"}},
+       } {
+               if err := checkEqual(data[0], data[1]); err != nil {
+                       t.Error(err)
+               }
+       }
+       if err := roundTrip(m); err != nil {
+               t.Error(err)
+       }
+
+       func() { // Expect a panic
+               defer func() {
+                       if x, ok := recover().(*UnmarshalError); !ok {
+                               t.Errorf("Expected UnmarshalError, got %#v", x)
+                       }
+               }()
+               m.Properties()
+               t.Error("Expected panic")
+       }()
+}
+
+func TestDeprecated(t *testing.T) {
+       m := NewMessage()
+
+       m.SetInstructions(map[string]interface{}{"instructions": "foo"})
+       m.SetAnnotations(map[string]interface{}{"annotations": "bar"})
+       m.SetProperties(map[string]interface{}{"int": int32(32), "bool": true})
+
+       for _, data := range [][]interface{}{
+               {m.DeliveryAnnotations(), 
map[Key]interface{}{SymbolKey("instructions"): "foo"}},
+               {m.MessageAnnotations(), 
map[Key]interface{}{SymbolKey("annotations"): "bar"}},
+               {m.ApplicationProperties(), 
map[Key]interface{}{SymbolKey("int"): int32(32), SymbolKey("bool"): true}},
+
+               {m.Instructions(), map[string]interface{}{"instructions": 
"foo"}},
+               {m.Annotations(), map[string]interface{}{"annotations": "bar"}},
+               {m.Properties(), map[string]interface{}{"int": int32(32), 
"bool": true}},
        } {
                if err := checkEqual(data[0], data[1]); err != nil {
                        t.Error(err)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/110f851a/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 2852c23..76f223f 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
@@ -105,6 +105,7 @@ type List []interface{}
 // Symbol is a string that is encoded as an AMQP symbol
 type Symbol string
 
+func (s Symbol) String() string   { return string(s) }
 func (s Symbol) GoString() string { return fmt.Sprintf("s\"%s\"", s) }
 
 // Binary is a string that is encoded as an AMQP binary.
@@ -112,6 +113,7 @@ func (s Symbol) GoString() string { return 
fmt.Sprintf("s\"%s\"", s) }
 // a map key, AMQP frequently uses binary types as map keys. It can convert to 
and from []byte
 type Binary string
 
+func (b Binary) String() string   { return string(b) }
 func (b Binary) GoString() string { return fmt.Sprintf("b\"%s\"", b) }
 
 // GoString for Map prints values with their types, useful for debugging.
@@ -192,3 +194,19 @@ func cPtr(b []byte) *C.char {
 func cLen(b []byte) C.size_t {
        return C.size_t(len(b))
 }
+
+// Key is used as a map key for some AMQP "restricted" maps which are
+// allowed to have keys that are either symbol or ulong but no other type.
+//
+type Key struct {
+       value interface{}
+}
+
+func SymbolKey(v Symbol) Key { return Key{v} }
+func IntKey(v uint64) Key    { return Key{v} }
+func StringKey(v string) Key { return Key{Symbol(v)} }
+
+// Returns the value which must be Symbol, uint64 or nil
+func (k Key) Get() interface{} { return k.value }
+
+func (k Key) String() string { return fmt.Sprintf("%v", k.Get()) }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/110f851a/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
new file mode 100644
index 0000000..b9c0596
--- /dev/null
+++ b/proton-c/bindings/go/src/qpid.apache.org/amqp/types_test.go
@@ -0,0 +1,34 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+package amqp
+
+import (
+       "fmt"
+)
+
+func ExampleKey() {
+       var k Key = SymbolKey(Symbol("foo"))
+       fmt.Println(k.Get().(Symbol))
+       k = IntKey(42)
+       fmt.Println(k.Get().(uint64))
+       // Output:
+       // foo
+       // 42
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/110f851a/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 d56cbd2..96b8e05 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
@@ -178,6 +178,8 @@ Types are converted as follows:
  |                           
+------------------------+---------------------------------------------+
  |                           |list                    |List                    
                     |
  
+---------------------------+------------------------+---------------------------------------------+
+ |Key                        |symbol, ulong                                    
                     |
+ 
+---------------------------+----------------------------------------------------------------------+
 
 The following Go types cannot be unmarshaled: uintptr, function, interface, 
channel.
 
@@ -431,6 +433,13 @@ func unmarshal(v interface{}, data *C.pn_data_t) {
        case *interface{}:
                getInterface(data, v)
 
+       case *Key:
+               if pnType == C.PN_ULONG || pnType == C.PN_SYMBOL || pnType == 
C.PN_STRING {
+                       unmarshal(&v.value, data)
+               } else {
+                       panic(newUnmarshalError(pnType, v))
+               }
+
        default:
                if reflect.TypeOf(v).Kind() != reflect.Ptr {
                        panic(newUnmarshalError(pnType, v))


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to