THRIFT-2337 Golang does not report TIMED_OUT exceptions

Patch: Chris Bannister
diff --git a/lib/go/thrift/transport_exception.go b/lib/go/thrift/transport_exception.go
index dbab4d9..9505b44 100644
--- a/lib/go/thrift/transport_exception.go
+++ b/lib/go/thrift/transport_exception.go
@@ -20,13 +20,19 @@
 package thrift
 
 import (
+	"errors"
 	"io"
 )
 
+type timeoutable interface {
+	Timeout() bool
+}
+
 // Thrift Transport exception
 type TTransportException interface {
 	TException
 	TypeId() int
+	Err() error
 }
 
 const (
@@ -38,8 +44,8 @@
 )
 
 type tTransportException struct {
-	typeId  int
-	message string
+	typeId int
+	err    error
 }
 
 func (p *tTransportException) TypeId() int {
@@ -47,22 +53,38 @@
 }
 
 func (p *tTransportException) Error() string {
-	return p.message
+	return p.err.Error()
 }
 
-func NewTTransportException(t int, m string) TTransportException {
-	return &tTransportException{typeId: t, message: m}
+func (p *tTransportException) Err() error {
+	return p.err
+}
+
+func NewTTransportException(t int, e string) TTransportException {
+	return &tTransportException{typeId: t, err: errors.New(e)}
 }
 
 func NewTTransportExceptionFromError(e error) TTransportException {
 	if e == nil {
 		return nil
 	}
+
 	if t, ok := e.(TTransportException); ok {
 		return t
 	}
-	if e == io.EOF {
-		return NewTTransportException(END_OF_FILE, e.Error())
+
+	switch v := e.(type) {
+	case TTransportException:
+		return v
+	case timeoutable:
+		if v.Timeout() {
+			return &tTransportException{typeId: TIMED_OUT, err: e}
+		}
 	}
-	return NewTTransportException(UNKNOWN_TRANSPORT_EXCEPTION, e.Error())
+
+	if e == io.EOF {
+		return &tTransportException{typeId: END_OF_FILE, err: e}
+	}
+
+	return &tTransportException{typeId: UNKNOWN_TRANSPORT_EXCEPTION, err: e}
 }
diff --git a/lib/go/thrift/transport_exception_test.go b/lib/go/thrift/transport_exception_test.go
new file mode 100644
index 0000000..55b0a77
--- /dev/null
+++ b/lib/go/thrift/transport_exception_test.go
@@ -0,0 +1,41 @@
+package thrift
+
+import (
+	"fmt"
+	"io"
+
+	"testing"
+)
+
+type timeout struct{ timedout bool }
+
+func (t *timeout) Timeout() bool {
+	return t.timedout
+}
+
+func (t *timeout) Error() string {
+	return fmt.Sprintf("Timeout: %v", t.timedout)
+}
+
+func TestTExceptionTimeout(t *testing.T) {
+	timeout := &timeout{true}
+	exception := NewTTransportExceptionFromError(timeout)
+	if timeout.Error() != exception.Error() {
+		t.Fatalf("Error did not match: expected %q, got %q", timeout.Error(), exception.Error())
+	}
+
+	if exception.TypeId() != TIMED_OUT {
+		t.Fatalf("TypeId was not TIMED_OUT: expected %v, got %v", TIMED_OUT, exception.TypeId())
+	}
+}
+
+func TestTExceptionEOF(t *testing.T) {
+	exception := NewTTransportExceptionFromError(io.EOF)
+	if io.EOF.Error() != exception.Error() {
+		t.Fatalf("Error did not match: expected %q, got %q", io.EOF.Error(), exception.Error())
+	}
+
+	if exception.TypeId() != END_OF_FILE {
+		t.Fatalf("TypeId was not END_OF_FILE: expected %v, got %v", END_OF_FILE, exception.TypeId())
+	}
+}