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]
