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/03 06:28:46 UTC
[2/2] qpid-proton git commit: PROTON-827: go binding - unmarshalling
AMQP string, symbol and binary types into Go types.
PROTON-827: go binding - unmarshalling AMQP string, symbol and binary types into Go types.
Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/84bea240
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/84bea240
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/84bea240
Branch: refs/heads/master
Commit: 84bea2403648379ae4da84601662e923282044bb
Parents: 5d8030f
Author: Alan Conway <ac...@redhat.com>
Authored: Mon Mar 2 10:10:14 2015 -0500
Committer: Alan Conway <ac...@redhat.com>
Committed: Tue Mar 3 00:27:52 2015 -0500
----------------------------------------------------------------------
proton-c/bindings/go/README.md | 35 +++
.../go/src/apache.org/proton/encoding.go | 216 +++++++++++++++++++
.../bindings/go/src/apache.org/proton/error.go | 23 +-
.../go/src/apache.org/proton/interop_test.go | 121 +++++++++++
.../bindings/go/src/apache.org/proton/types.go | 25 +++
.../bindings/go/src/apache.org/proton/url.go | 2 +-
6 files changed, 414 insertions(+), 8 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/84bea240/proton-c/bindings/go/README.md
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/README.md b/proton-c/bindings/go/README.md
index 5ae8ca0..31bdb5d 100644
--- a/proton-c/bindings/go/README.md
+++ b/proton-c/bindings/go/README.md
@@ -94,5 +94,40 @@ also supports traditional locking, so we could adopt locking strategies similar
to our other bindings, but we should investigate the Go-like alternatives. There
are analogies between Go channels and AMQP links that we will probably exploit.
+## Implementation status
+Working on API to marshal/unmarshal AMQP data into Go types.
+
+The API will follow the style of the standard libraries encoding/json and encoding/xml.
+
+To be done:
+
+Easy unmarshaling into native Go types:
+
+- String-like AMQP types (symbol, binary, string) into Go string or []byte
+- Numeric AMQP types into any Go numeric (numeric conversion)
+- Any AMQP type into GO reflect.Value choosing the closest native Go type
+- AMQP map into go struct if keys match struct field names and values match field types
+- AMQP maps into map[K]T if all AMQP keys/values can convert to K and T (reflect.Value allowed)
+- AMQP list into []T if all list elements can convert to T (reflect.Value allowed)
+
+Easy marshaling of native Go types:
+
+- Go struct: amqp map with field names as string keys (use tags to customize?)
+- Go string to AMQP string, Go []byte to AMQP binary, Go numerics to closest AMQP numeric
+- Go []T to AMQP list
+- Go map to AMQP map
+
+Customization:
+
+- Standard encoding libraries define Marshaler and Unmarshaler interfaces.
+- User implements to customize behavior of a user type.
+- Does this require exposing the proton codec?
+
+Exact (strict) (un)marshaling:
+
+- Define special Go AMQP types that exactly reflect AMQP types & encodings.
+- Unmarshal to special types only if exact match for wire
+- Marshal special types exactly
+- Define AMQPValue which can unmarshal from any AMQP type using strict unmarshaling types.
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/84bea240/proton-c/bindings/go/src/apache.org/proton/encoding.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/encoding.go b/proton-c/bindings/go/src/apache.org/proton/encoding.go
new file mode 100644
index 0000000..eb42c8c
--- /dev/null
+++ b/proton-c/bindings/go/src/apache.org/proton/encoding.go
@@ -0,0 +1,216 @@
+/*
+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 proton
+
+//#include <proton/codec.h>
+import "C"
+
+import (
+ "bytes"
+ "io"
+ "reflect"
+ "unsafe"
+)
+
+//
+// Decoding from a pn_data_t
+//
+
+type pnDecoder struct{ data *C.pn_data_t }
+
+func newPnDecoder() pnDecoder { return pnDecoder{C.pn_data(0)} }
+func (pd pnDecoder) free() { C.pn_data_free((*C.pn_data_t)(pd.data)) }
+
+// decode from bytes. Return bytes decoded and errEOS if we run out of data.
+func (pd pnDecoder) decode(bytes []byte) (n int, err error) {
+ C.pn_data_clear(pd.data)
+ if len(bytes) == 0 {
+ return 0, errEOS
+ }
+ cBuf := (*C.char)(unsafe.Pointer(&bytes[0]))
+ result := int(C.pn_data_decode(pd.data, cBuf, C.size_t(len(bytes))))
+ if result < 0 {
+ return 0, errorCode(result)
+ } else {
+ return result, nil
+ }
+}
+
+// Unmarshal decodes bytes and converts into the value pointed to by v.
+//
+// Returns the number of bytes decoded and an errorCode on error.
+func (pd pnDecoder) unmarshal(bytes []byte, v interface{}) (n int, err error) {
+ n, err = pd.decode(bytes)
+ if err != nil {
+ return
+ }
+ switch v := v.(type) {
+ case *string:
+ err = pd.unmarshalString(v)
+ case *[]byte:
+ err = pd.unmarshalBytes(v)
+ case *Symbol:
+ err = pd.unmarshalSymbol(v)
+ default:
+ note := ""
+ if reflect.TypeOf(v).Kind() != reflect.Ptr {
+ note = "is not a pointer"
+ }
+ return 0, errorf("Unmarshal bad type: %T %s", v, note)
+ // FIXME aconway 2015-03-02: not finished
+ }
+ if err != nil {
+ return 0, err
+ }
+ return
+}
+
+func (pd pnDecoder) unmarshalPnBytes(target string) (pnBytes C.pn_bytes_t, err error) {
+ switch amqpType := C.pn_data_type(pd.data); amqpType {
+ case C.PN_STRING:
+ pnBytes = C.pn_data_get_string(pd.data)
+ case C.PN_BINARY:
+ pnBytes = C.pn_data_get_binary(pd.data)
+ case C.PN_SYMBOL:
+ pnBytes = C.pn_data_get_symbol(pd.data)
+ default:
+ // FIXME aconway 2015-03-02: error message - json style UnmarsalTypeError?
+ return C.pn_bytes_t{}, errorf("Unmarshal cannot convert %#v to %s", amqpType, target)
+ }
+ return pnBytes, nil
+}
+
+func (pd pnDecoder) unmarshalString(v *string) error {
+ pnBytes, err := pd.unmarshalPnBytes("string")
+ if err == nil {
+ *v = C.GoStringN(pnBytes.start, C.int(pnBytes.size))
+ }
+ return err
+}
+
+func (pd pnDecoder) unmarshalBytes(v *[]byte) error {
+ pnBytes, err := pd.unmarshalPnBytes("[]byte")
+ *v = C.GoBytes(unsafe.Pointer(pnBytes.start), C.int(pnBytes.size))
+ return err
+}
+
+func (pd pnDecoder) unmarshalSymbol(v *Symbol) error {
+ pnBytes, err := pd.unmarshalPnBytes("symbol")
+ if err == nil {
+ *v = Symbol(C.GoStringN(pnBytes.start, C.int(pnBytes.size)))
+ }
+ return err
+}
+
+/*
+Unmarshal decodes AMQP-encoded bytes and stores the result in the value pointed to by v.
+
+FIXME mapping details
+
+ +-------------------------------+-----------------------------------------------+
+ |AMQP type |Go type |
+ +-------------------------------+-----------------------------------------------+
+ |string |string |
+ +-------------------------------+-----------------------------------------------+
+ |symbol |proton.Symbol |
+ +-------------------------------+-----------------------------------------------+
+ |binary |[]byte |
+ +-------------------------------+-----------------------------------------------+
+*/
+func Unmarshal(bytes []byte, v interface{}) error {
+ pd := newPnDecoder()
+ defer pd.free()
+ _, err := pd.unmarshal(bytes, v)
+ return err
+}
+
+// Decoder decodes AMQP values from an io.Reader.
+//
+type Decoder struct {
+ reader io.Reader
+ buffer bytes.Buffer
+ readErr error // Outstanding error on our reader
+}
+
+// NewDecoder returns a new decoder that reads from r.
+//
+// The decoder has it's own buffer and may read more data than required for the
+// AMQP values requested. Use Buffered to see if there is data left in the
+// buffer.
+//
+func NewDecoder(r io.Reader) *Decoder {
+ return &Decoder{r, bytes.Buffer{}, nil}
+}
+
+// Buffered returns a reader of the data remaining in the Decoder's buffer. The
+// reader is valid until the next call to Decode.
+//
+func (d *Decoder) Buffered() io.Reader {
+ return bytes.NewReader(d.buffer.Bytes())
+}
+
+// more reads more data when we can't parse a complete AMQP type
+func (d *Decoder) more() error {
+ if d.readErr != nil { // Reader already broken, give up
+ return d.readErr
+ }
+ var readSize int64 = 256
+ if int64(d.buffer.Len()) > readSize { // Grow by doubling
+ readSize = int64(d.buffer.Len())
+ }
+ var n int64
+ n, d.readErr = d.buffer.ReadFrom(&io.LimitedReader{d.reader, readSize})
+ if n == 0 { // ReadFrom won't report io.EOF, just returns 0
+ if d.readErr != nil {
+ return d.readErr
+ } else {
+ return errorf("no data")
+ }
+ }
+ return nil
+}
+
+// Decode reads the next AMQP value from the Reader and stores it in the value pointed to by v.
+//
+// See the documentation for Unmarshal for details about the conversion of AMQP into a Go value.
+//
+func (d *Decoder) Decode(v interface{}) (err error) {
+ pd := newPnDecoder()
+ defer pd.free()
+
+ // On errEOS, read more data and try again till we have a complete pn_data.
+ for {
+ var n int
+ n, err = pd.unmarshal(d.buffer.Bytes(), v)
+ switch err {
+ case nil:
+ d.buffer.Next(n)
+ return
+ case errEOS:
+ err = d.more()
+ if err != nil {
+ return err
+ }
+ default:
+ return err
+ }
+ }
+ return err
+}
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/84bea240/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 178303e..51cde28 100644
--- a/proton-c/bindings/go/src/apache.org/proton/error.go
+++ b/proton-c/bindings/go/src/apache.org/proton/error.go
@@ -71,12 +71,21 @@ func (code errorCode) Error() string {
return fmt.Sprintf("proton: %v", code)
}
-// errorf formats an error message with a proton: prefix.
-func errorf(format string, a ...interface{}) error {
- return fmt.Errorf("proton: %v", fmt.Sprintf(format, a...))
-}
+// pnError is a simple error string.
+//
+// NOTE: All error types used in proton have both 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
-// errorf2 formats an error message with a proton: prefix and an inner error message.
-func errorf2(err error, format string, a ...interface{}) error {
- return fmt.Errorf("proton: %v: %v", fmt.Sprintf(format, a...), err)
+func (err pnError) String() string { return string(err) }
+func (err pnError) Error() string { return fmt.Sprintf("proton: %s", string(err)) }
+
+// errorf creates an error with a formatted message
+func errorf(format string, a ...interface{}) error {
+ return pnError(fmt.Sprintf(format, a...))
}
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/84bea240/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
new file mode 100644
index 0000000..db988d2
--- /dev/null
+++ b/proton-c/bindings/go/src/apache.org/proton/interop_test.go
@@ -0,0 +1,121 @@
+/*
+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.
+*/
+
+// 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 (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "reflect"
+ "testing"
+)
+
+func getReader(t *testing.T, name string) (r io.Reader) {
+ r, err := os.Open("../../../../../../tests/interop/" + name + ".amqp")
+ if err != nil {
+ t.Fatalf("Can't open %#v: %v", name, err)
+ }
+ return
+}
+
+func remaining(d *Decoder) string {
+ remainder, _ := ioutil.ReadAll(io.MultiReader(d.Buffered(), d.reader))
+ return string(remainder)
+}
+
+// Expectation of a test, want is the expected value, got is a pointer to a
+// instance of the same type, which will be replaced by Decode.
+type expect struct {
+ want, got interface{}
+}
+
+// checkDecode: want is the expected value, gotPtr is a pointer to a
+// instance of the same type for Decode.
+func checkDecode(d *Decoder, want interface{}, gotPtr interface{}) error {
+ err := d.Decode(gotPtr)
+ if err != nil {
+ return err
+ }
+ got := reflect.ValueOf(gotPtr).Elem().Interface()
+ if !reflect.DeepEqual(want, got) {
+ return fmt.Errorf("%#v != %#v", want, got)
+ }
+ return nil
+}
+
+func TestUnmarshal(t *testing.T) {
+ bytes, err := ioutil.ReadAll(getReader(t, "strings"))
+ if err != nil {
+ t.Error(err)
+ }
+ var got string
+ err = Unmarshal(bytes, &got)
+ if err != nil {
+ t.Error(err)
+ }
+ want := "abc\000defg"
+ if want != got {
+ t.Errorf("%#v != %#v", want, got)
+ }
+}
+
+func TestStrings(t *testing.T) {
+ d := NewDecoder(getReader(t, "strings"))
+ // Test decoding as plain Go strings
+ for i, want := range []string{"abc\000defg", "abcdefg", "abcdefg", "", "", ""} {
+ var got string
+ if err := checkDecode(d, want, &got); err != nil {
+ t.Errorf("%d: %v", i, err)
+ }
+ }
+ remains := remaining(d)
+ if remains != "" {
+ t.Errorf("leftover: %s", remains)
+ }
+
+ // Test decoding as specific string types
+ d = NewDecoder(getReader(t, "strings"))
+ var bytes []byte
+ var str string
+ var sym Symbol
+ for i, expect := range []expect{
+ {[]byte("abc\000defg"), &bytes},
+ {"abcdefg", &str},
+ {Symbol("abcdefg"), &sym},
+ {make([]byte, 0), &bytes},
+ {"", &str},
+ {Symbol(""), &sym},
+ } {
+ if err := checkDecode(d, expect.want, expect.got); err != nil {
+ t.Errorf("%d: %v", i, err)
+ }
+ }
+ remains = remaining(d)
+ if remains != "" {
+ t.Errorf("leftover: %s", remains)
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/84bea240/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
new file mode 100644
index 0000000..bde1ddd
--- /dev/null
+++ b/proton-c/bindings/go/src/apache.org/proton/types.go
@@ -0,0 +1,25 @@
+/*
+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 proton
+
+// Types to exactly represent specific AMQP encodings
+
+// Symbol is the AMQP symbol data type, it can be converted to a Go string or []byte
+type Symbol string
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/84bea240/proton-c/bindings/go/src/apache.org/proton/url.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/url.go b/proton-c/bindings/go/src/apache.org/proton/url.go
index 44ef4ba..1511203 100644
--- a/proton-c/bindings/go/src/apache.org/proton/url.go
+++ b/proton-c/bindings/go/src/apache.org/proton/url.go
@@ -66,7 +66,7 @@ func ParseURL(s string) (u *url.URL, err error) {
path := C.GoString(C.pn_url_get_path(pnUrl))
if err != nil {
- return nil, errorf2(err, "bad URL %#v", s)
+ return nil, errorf("bad URL %#v: %s", s, err)
}
if scheme == "" {
scheme = AMQP
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org