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