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 + 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 + 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) + } +}