diff --git a/go.mod b/go.mod
index 716cd3d..84cb904 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,11 @@
module github.com/tiaguinho/gosoap
require (
+ github.com/google/go-cmp v0.5.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
golang.org/x/text v0.3.2 // indirect
+ gotest.tools v2.2.0+incompatible
)
go 1.13
diff --git a/go.sum b/go.sum
index 5afc6ff..1580ca7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -7,3 +11,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
diff --git a/response.go b/response.go
index e68a15e..e11682a 100644
--- a/response.go
+++ b/response.go
@@ -12,16 +12,45 @@ type Response struct {
Payload []byte
}
+// FaultError implements error interface
+type FaultError struct {
+ fault *Fault
+}
+
+func (e FaultError) Error() string {
+ if e.fault != nil {
+ return e.fault.String()
+ }
+
+ return ""
+}
+
+// IsFault returns whether the given error is a fault error or not.
+//
+// IsFault will return false when the error could not be typecasted to FaultError, because
+// every fault error should have it's dynamic type as FaultError.
+func IsFault(err error) bool {
+ if _, ok := err.(FaultError); !ok {
+ return false
+ }
+
+ return true
+}
+
// Unmarshal get the body and unmarshal into the interface
func (r *Response) Unmarshal(v interface{}) error {
if len(r.Body) == 0 {
return fmt.Errorf("Body is empty")
}
- var f Fault
- xml.Unmarshal(r.Body, &f)
- if f.Code != "" {
- return fmt.Errorf("[%s]: %s", f.Code, f.Description)
+ var fault Fault
+ err := xml.Unmarshal(r.Body, &fault)
+ if err != nil {
+ return fmt.Errorf("error unmarshalling the body to Fault: %v", err.Error())
+ }
+
+ if fault.Code != "" {
+ return FaultError{fault: &fault}
}
return xml.Unmarshal(r.Body, v)
diff --git a/response_test.go b/response_test.go
new file mode 100644
index 0000000..d2f41ba
--- /dev/null
+++ b/response_test.go
@@ -0,0 +1,128 @@
+package gosoap
+
+import (
+ "encoding/xml"
+ "fmt"
+ "testing"
+
+ "gotest.tools/assert"
+)
+
+func TestUnmarshal(t *testing.T) {
+ var testCases = []struct {
+ description string
+ response *Response
+ decodeStruct interface{}
+ isFaultError bool
+ }{
+ {
+ description: "case: fault error",
+ response: &Response{
+ Body: []byte(`
+
+ soap:Server
+ Qube.Mama.SoapException: The remote server returned an error: (550) File unavailable (e.g., file not found, no access).
+ The remote server returned an error: (550) File unavailable (e.g., file not found, no access).
+
+
+
+
+ `),
+ },
+ decodeStruct: &struct{}{},
+ isFaultError: true,
+ },
+ {
+ description: "case: unmarshal error",
+ response: &Response{
+ Body: []byte(`
+
+
+
+ 9e7d58d9-6f62-43e3-b189-5b1b58eea629
+ Completed
+
+ 0
+ 0
+
+
+
+
+ `),
+ },
+ decodeStruct: &struct {
+ XMLName xml.Name `xml:"GetJobsByIsResponse"`
+ GetJobsByIDsResult string
+ }{},
+ isFaultError: false,
+ },
+ {
+ description: "case: nil error",
+ response: &Response{
+ Body: []byte(`
+
+
+
+ 9e7d58d9-6f62-43e3-b189-5b1b58eea629
+ Completed
+
+ 0
+ 0
+
+
+
+
+ `),
+ },
+ decodeStruct: &struct {
+ XMLName xml.Name `xml:"GetJobsByIdsResponse"`
+ GetJobsByIDsResult string
+ }{},
+ isFaultError: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Logf("running %v test case", testCase.description)
+
+ err := testCase.response.Unmarshal(testCase.decodeStruct)
+ assert.Equal(t, testCase.isFaultError, IsFault(err))
+ }
+}
+
+func TestIsFault(t *testing.T) {
+ var testCases = []struct {
+ description string
+ err error
+ expectedIsFaultError bool
+ }{
+ {
+ description: "case: fault error",
+ err: FaultError{
+ fault: &Fault{
+ Code: "SOAP-ENV:Client",
+ },
+ },
+ expectedIsFaultError: true,
+ },
+ {
+ description: "case: unmarshal error",
+ err: fmt.Errorf("unmarshall err: .."),
+ expectedIsFaultError: false,
+ },
+ {
+ description: "case: nil error",
+ err: nil,
+ expectedIsFaultError: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Logf("running %v test case", testCase.description)
+
+ isFaultErr := IsFault(testCase.err)
+ assert.Equal(t, testCase.expectedIsFaultError, isFaultErr)
+ }
+}
diff --git a/wsdl.go b/wsdl.go
index 28d734d..ef98794 100644
--- a/wsdl.go
+++ b/wsdl.go
@@ -2,6 +2,7 @@ package gosoap
import (
"encoding/xml"
+ "fmt"
"io"
"net/http"
"net/url"
@@ -211,8 +212,13 @@ func (wsdl *wsdlDefinitions) GetSoapActionFromWsdlOperation(operation string) st
}
// Fault response
+// Fault implements Stringer interface
type Fault struct {
Code string `xml:"faultcode"`
Description string `xml:"faultstring"`
Detail string `xml:"detail"`
}
+
+func (f *Fault) String() string {
+ return fmt.Sprintf("[%s]: %s", f.Code, f.Description)
+}
diff --git a/wsdl_test.go b/wsdl_test.go
index d230b6a..7780f90 100644
--- a/wsdl_test.go
+++ b/wsdl_test.go
@@ -6,6 +6,8 @@ import (
"runtime"
"strings"
"testing"
+
+ "gotest.tools/assert"
)
func Test_getWsdlBody(t *testing.T) {
@@ -66,3 +68,27 @@ func Test_getWsdlBody(t *testing.T) {
})
}
}
+
+func TestFaultString(t *testing.T) {
+ var testCases = []struct {
+ description string
+ fault *Fault
+ expectedFaultStr string
+ }{
+ {
+ description: "success case: fault string",
+ fault: &Fault{
+ Code: "soap:SERVER",
+ Description: "soap exception",
+ },
+ expectedFaultStr: "[soap:SERVER]: soap exception",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Logf("running %v testCase", testCase.description)
+
+ faultStr := testCase.fault.String()
+ assert.Equal(t, testCase.expectedFaultStr, faultStr)
+ }
+}