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:45 UTC
[1/2] qpid-proton git commit: PROTON-827: go binding - use
net/url.URL, error handling.
Repository: qpid-proton
Updated Branches:
refs/heads/master a0edd8dff -> 84bea2403
PROTON-827: go binding - use net/url.URL, error handling.
Replaced the proton.Url with the standard net/url.URL.
We still use the proton URL parser because it is much more forgiving and allows abbreviations
that the Go URL parser does not. However the result is stored in a net/url.URL.
Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/5d8030fa
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/5d8030fa
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/5d8030fa
Branch: refs/heads/master
Commit: 5d8030faa2ecf87a2328c2aa572856cb64b626e3
Parents: a0edd8d
Author: Alan Conway <ac...@redhat.com>
Authored: Mon Mar 2 16:57:55 2015 -0500
Committer: Alan Conway <ac...@redhat.com>
Committed: Mon Mar 2 16:57:55 2015 -0500
----------------------------------------------------------------------
.../bindings/go/src/apache.org/proton/error.go | 82 +++++++++++
.../bindings/go/src/apache.org/proton/url.go | 146 +++++--------------
.../go/src/apache.org/proton/url_test.go | 125 +++-------------
3 files changed, 138 insertions(+), 215 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/5d8030fa/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
new file mode 100644
index 0000000..178303e
--- /dev/null
+++ b/proton-c/bindings/go/src/apache.org/proton/error.go
@@ -0,0 +1,82 @@
+/*
+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/error.h>
+import "C"
+
+import (
+ "fmt"
+)
+
+// errorCode is an error code returned by proton C.
+type errorCode int
+
+const (
+ errEOS errorCode = C.PN_EOS
+ errError = C.PN_ERR
+ errOverflow = C.PN_OVERFLOW
+ errUnderflow = C.PN_UNDERFLOW
+ errState = C.PN_STATE_ERR
+ errArgument = C.PN_ARG_ERR
+ errTimeout = C.PN_TIMEOUT
+ errInterrupted = C.PN_INTR
+ errInProgress = C.PN_INPROGRESS
+)
+
+// String gives a brief description of an errorCode.
+func (code errorCode) String() string {
+ switch code {
+ case errEOS:
+ return "end of data"
+ case errError:
+ return "error"
+ case errOverflow:
+ return "overflow"
+ case errUnderflow:
+ return "underflow"
+ case errState:
+ return "bad state"
+ case errArgument:
+ return "invalid argument"
+ case errTimeout:
+ return "timeout"
+ case errInterrupted:
+ return "interrupted"
+ case errInProgress:
+ return "in progress"
+ }
+ return fmt.Sprintf("invalid error code %d", code)
+}
+
+// An errorCode can be used as an error
+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...))
+}
+
+// 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)
+}
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/5d8030fa/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 9c76b7d..44ef4ba 100644
--- a/proton-c/bindings/go/src/apache.org/proton/url.go
+++ b/proton-c/bindings/go/src/apache.org/proton/url.go
@@ -34,135 +34,63 @@ inline void set(pn_url_t *url, setter_fn s, const char* value) {
import "C"
import (
- "fmt"
"net"
+ "net/url"
"unsafe"
)
-// Url holds the fields of a parsed AMQP URL.
-//
-// An AMQP URL contains information
-// to connect to a server (like any URL) and a path string that is interpreted
-// by the remote server to identify an AMQP node such as a queue or topic.
-//
-// The fields are:
-//
-// Scheme: "amqp" or "amqps" for AMQP over SSL
-// Username: user name for authentication
-// Password: password for authentication
-// Host: Host address, can be a DNS name or an IP address literal (v4 or v6)
-// Port: Note this is a string, not a number. It an be a numeric value like "5672" or a service name like "amqp"
-// Path: Interpeted by the remote end as an AMQP node address.
-// PortInt: int value of port, set after calling Validate()
-//
-// A URL string has the form "scheme://username:password@host:port/path".
-// Partial URLs such as "host", "host:port", ":port" or "host/path" are allowed.
-//
-type Url struct {
- Scheme, Username, Password, Host, Port, Path string
- PortInt int
-}
-
-// urlFromC creates a Url and copies the fields from the C pn_url_t pnUrl
-func urlFromC(pnUrl *C.pn_url_t) Url {
- return Url{
- C.GoString(C.pn_url_get_scheme(pnUrl)),
- C.GoString(C.pn_url_get_username(pnUrl)),
- C.GoString(C.pn_url_get_password(pnUrl)),
- C.GoString(C.pn_url_get_host(pnUrl)),
- C.GoString(C.pn_url_get_port(pnUrl)),
- C.GoString(C.pn_url_get_path(pnUrl)),
- 0,
- }
-}
-
-// ParseUrl parses a URL string and returns a Url and an error if it is not valid.
-//
-// For a partial URL string, missing components will be filled in with default values.
-//
-func ParseUrl(s string) (u Url, err error) {
- u, err = ParseUrlRaw(s)
- if err == nil {
- err = u.Validate()
- }
- if err != nil {
- return Url{}, err
- }
- return
-}
+const (
+ AMQP string = "amqp"
+ AMQPS = "amqps"
+)
-// ParseUrlRaw parses a URL string and returns a Url and an error if it is not valid.
+// ParseUrl parses an AMQP URL string and returns a net/url.Url.
//
-// For a partial URL string, missing components will be empty strings "" in the Url struct.
-// Error checking is limited, more errors are checked in Validate()
+// It is more forgiving than net/url.Parse and allows most of the parts of the
+// URL to be missing, assuming AMQP defaults.
//
-func ParseUrlRaw(s string) (u Url, err error) {
+func ParseURL(s string) (u *url.URL, err error) {
cstr := C.CString(s)
defer C.free(unsafe.Pointer(cstr))
pnUrl := C.pn_url_parse(cstr)
if pnUrl == nil {
- return Url{}, fmt.Errorf("proton: Invalid Url '%v'", s)
+ return nil, errorf("bad URL %#v", s)
}
defer C.pn_url_free(pnUrl)
- u = urlFromC(pnUrl)
- return
-}
-// newPnUrl creates a C pn_url_t and populate it with the fields from url.
-func newPnUrl(url Url) *C.pn_url_t {
- pnUrl := C.pn_url()
- set := func(setter unsafe.Pointer, value string) {
- if value != "" {
- cstr := C.CString(value)
- defer C.free(unsafe.Pointer(cstr))
- C.set(pnUrl, C.setter_fn(setter), cstr)
- }
- }
- set(C.pn_url_set_scheme, url.Scheme)
- set(C.pn_url_set_username, url.Username)
- set(C.pn_url_set_password, url.Password)
- set(C.pn_url_set_host, url.Host)
- set(C.pn_url_set_port, url.Port)
- set(C.pn_url_set_path, url.Path)
- return pnUrl
-}
+ scheme := C.GoString(C.pn_url_get_scheme(pnUrl))
+ username := C.GoString(C.pn_url_get_username(pnUrl))
+ password := C.GoString(C.pn_url_get_password(pnUrl))
+ host := C.GoString(C.pn_url_get_host(pnUrl))
+ port := C.GoString(C.pn_url_get_port(pnUrl))
+ path := C.GoString(C.pn_url_get_path(pnUrl))
-// String generates a representation of the Url.
-func (u Url) String() string {
- pnUrl := newPnUrl(u)
- defer C.pn_url_free(pnUrl)
- return C.GoString(C.pn_url_str(pnUrl))
-}
-
-// Validate supplies defaults for Scheme, Host and Port and does additional error checks.
-//
-// Fills in the value of PortInt from Port.
-//
-func (u *Url) Validate() error {
- if u.Scheme == "" {
- u.Scheme = "amqp"
+ if err != nil {
+ return nil, errorf2(err, "bad URL %#v", s)
}
- if u.Host == "" {
- u.Host = "127.0.0.1"
+ if scheme == "" {
+ scheme = AMQP
}
- if u.Port == "" {
- if u.Scheme == "amqps" {
- u.Port = "amqps"
+ if port == "" {
+ if scheme == AMQPS {
+ port = AMQPS
} else {
- u.Port = "amqp"
+ port = AMQP
}
}
- port, err := net.LookupPort("", u.Port)
- u.PortInt = port
- if u.PortInt == 0 || err != nil {
- return fmt.Errorf("proton: Invalid Url '%v' (bad port '%v')", u, u.Port)
+ var user *url.Userinfo
+ if password != "" {
+ user = url.UserPassword(username, password)
+ } else if username != "" {
+ user = url.User(username)
+ }
+
+ u = &url.URL{
+ Scheme: scheme,
+ User: user,
+ Host: net.JoinHostPort(host, port),
+ Path: path,
}
- return nil
-}
-// Equals tests for equality between two Urls.
-func (u Url) Equals(x Url) bool {
- return (&u == &x) || (u.Scheme == x.Scheme &&
- u.Username == x.Username && u.Password == x.Password &&
- u.Host == x.Host && u.Port == x.Port && u.Path == x.Path)
+ return u, nil
}
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/5d8030fa/proton-c/bindings/go/src/apache.org/proton/url_test.go
----------------------------------------------------------------------
diff --git a/proton-c/bindings/go/src/apache.org/proton/url_test.go b/proton-c/bindings/go/src/apache.org/proton/url_test.go
index 3174769..7315511 100644
--- a/proton-c/bindings/go/src/apache.org/proton/url_test.go
+++ b/proton-c/bindings/go/src/apache.org/proton/url_test.go
@@ -21,118 +21,31 @@ package proton
import (
"fmt"
- "reflect"
- "testing"
)
-func checkEqual(t *testing.T, a, b interface{}) string {
- if !reflect.DeepEqual(a, b) {
- return fmt.Sprintf("%#v != %#v", a, b)
- }
- return ""
-}
-
-// Verify URL matches fields, check round trip compose/parse
-func checkUrl(t *testing.T, u Url, scheme, username, password, host, port, path string) string {
- if msg := checkEqual(t, u.Scheme, scheme); msg != "" {
- return msg
- }
- if msg := checkEqual(t, u.Username, username); msg != "" {
- return msg
- }
- if msg := checkEqual(t, u.Password, password); msg != "" {
- return msg
- }
- if msg := checkEqual(t, u.Host, host); msg != "" {
- return msg
- }
- if msg := checkEqual(t, u.Port, port); msg != "" {
- return msg
- }
- if msg := checkEqual(t, u.Path, path); msg != "" {
- return msg
- }
- u2, _ := ParseUrlRaw(u.String()) // Round trip compose/parse
- if msg := checkEqual(t, u.String(), u2.String()); msg != "" {
- return msg
- }
- return ""
-}
-
-func TestParse(t *testing.T) {
- s := "amqp://me:secret@myhost:1234/foobar"
- u, _ := ParseUrl(s)
- if msg := checkEqual(t, u.String(), s); msg != "" {
- t.Error(msg)
- }
- if msg := checkUrl(t, u, "amqp", "me", "secret", "myhost", "1234", "foobar"); msg != "" {
- t.Error(msg)
- }
-}
-
-// Test URL's with missing elements.
-func TestMissing(t *testing.T) {
- u := Url{Username: "me", Password: "secret", Host: "myhost", Path: "foobar"}
- if msg := checkUrl(t, u, "", "me", "secret", "myhost", "", "foobar"); msg != "" {
- t.Error(msg)
- }
- if msg := checkEqual(t, "me:secret@myhost/foobar", u.String()); msg != "" {
- t.Error(msg)
- }
- u, _ = ParseUrlRaw("myhost/")
- if msg := checkUrl(t, u, "", "", "", "myhost", "", ""); msg != "" {
- t.Error(msg)
- }
-}
-
-func TestDefaults(t *testing.T) {
- for pre, post := range map[string]string{
- "foo": "amqp://foo:amqp",
- ":1234": "amqp://127.0.0.1:1234",
- "/path": "amqp://127.0.0.1:amqp/path",
- "amqp://": "amqp://127.0.0.1:amqp",
- "amqps://": "amqps://127.0.0.1:amqps",
+func ExampleParseURL() {
+ for _, s := range []string{
+ "amqp://username:password@host:1234/path",
+ "host:1234",
+ "host",
+ ":1234",
+ "host/path",
+ "amqps://host",
+ "",
} {
- url, _ := ParseUrl(pre)
- if msg := checkEqual(t, url.String(), post); msg != "" {
- t.Error(msg)
+ u, err := ParseURL(s)
+ if err != nil {
+ fmt.Println(err)
+ } else {
+ fmt.Println(u)
}
}
-}
-
-func ExampleParseUrl_1parse() {
- url, _ := ParseUrl("amqp://username:password@host:1234/path")
- fmt.Printf("%#v\n", url) // Show the struct fields.
- fmt.Println(url) // url.String() shows the string form
// Output:
- // proton.Url{Scheme:"amqp", Username:"username", Password:"password", Host:"host", Port:"1234", Path:"path", PortInt:1234}
// amqp://username:password@host:1234/path
-}
-
-func ExampleParseUrl_2defaults() {
- // Default settings for partial URLs
- url, _ := ParseUrl(":5672")
- fmt.Println(url)
- url, _ = ParseUrl("host/path")
- fmt.Println(url)
- url, _ = ParseUrl("amqp://")
- fmt.Printf("%v port=%v\n", url, url.PortInt)
- url, _ = ParseUrl("amqps://")
- fmt.Printf("%v port=%v\n", url, url.PortInt)
- // Output:
- // amqp://127.0.0.1:5672
+ // amqp://host:1234
+ // amqp://host:amqp
+ // amqp://:1234
// amqp://host:amqp/path
- // amqp://127.0.0.1:amqp port=5672
- // amqps://127.0.0.1:amqps port=5671
-}
-
-func ExampleParseUrl_3invalid() {
- // Invalid URLs
- _, err := ParseUrl("")
- fmt.Println(err)
- _, err = ParseUrl(":foobar")
- fmt.Println(err)
- // Output:
- // proton: Invalid Url ''
- // proton: Invalid Url 'amqp://127.0.0.1:foobar' (bad port 'foobar')
+ // amqps://host:amqps
+ // proton: bad URL ""
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[2/2] qpid-proton git commit: PROTON-827: go binding - unmarshalling
AMQP string, symbol and binary types into Go types.
Posted by ac...@apache.org.
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