From 211e68018961e2bce2147f0758c3fdb820be3f67 Mon Sep 17 00:00:00 2001 From: Yousuf Ahmed Date: Tue, 5 Mar 2024 11:44:18 -0500 Subject: [PATCH 1/5] allow call with soap envelope --- soap/MMAEncoder.go | 3 +-- soap/MTOMEncoder.go | 3 +-- soap/soap.go | 39 ++++++++++++++++++++------------------- soap/soap_test.go | 18 +++++++++--------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/soap/MMAEncoder.go b/soap/MMAEncoder.go index 42579e20..6b8de826 100644 --- a/soap/MMAEncoder.go +++ b/soap/MMAEncoder.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "mime" "mime/multipart" "net/textproto" @@ -130,7 +129,7 @@ func (d *mmaDecoder) Decode(v interface{}) error { if contentID == "" { return errors.New("Invalid multipart content ID") } - content, err := ioutil.ReadAll(p) + content, err := io.ReadAll(p) if err != nil { return err } diff --git a/soap/MTOMEncoder.go b/soap/MTOMEncoder.go index 640073e3..22e61398 100644 --- a/soap/MTOMEncoder.go +++ b/soap/MTOMEncoder.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "math/rand" "mime" "mime/multipart" @@ -253,7 +252,7 @@ func (d *mtomDecoder) Decode(v interface{}) error { if contentID == "" { return errors.New("Invalid multipart content ID") } - content, err := ioutil.ReadAll(p) + content, err := io.ReadAll(p) if err != nil { return err } diff --git a/soap/soap.go b/soap/soap.go index 26adef50..56e2a1ea 100644 --- a/soap/soap.go +++ b/soap/soap.go @@ -7,7 +7,6 @@ import ( "encoding/xml" "fmt" "io" - "io/ioutil" "net" "net/http" "time" @@ -374,48 +373,50 @@ func (s *Client) SetHeaders(headers ...interface{}) { } // CallContext performs HTTP POST request with a context -func (s *Client) CallContext(ctx context.Context, soapAction string, request, response interface{}) error { - return s.call(ctx, soapAction, request, response, nil, nil) +func (s *Client) CallContext(ctx context.Context, soapAction string, envelope, request, response interface{}) error { + return s.call(ctx, soapAction, envelope, request, response, nil, nil) } // Call performs HTTP POST request. // Note that if the server returns a status code >= 400, a HTTPError will be returned -func (s *Client) Call(soapAction string, request, response interface{}) error { - return s.call(context.Background(), soapAction, request, response, nil, nil) +func (s *Client) Call(soapAction string, envelope, request, response interface{}) error { + return s.call(context.Background(), soapAction, envelope, request, response, nil, nil) } // CallContextWithAttachmentsAndFaultDetail performs HTTP POST request. // Note that if SOAP fault is returned, it will be stored in the error. // On top the attachments array will be filled with attachments returned from the SOAP request. -func (s *Client) CallContextWithAttachmentsAndFaultDetail(ctx context.Context, soapAction string, request, +func (s *Client) CallContextWithAttachmentsAndFaultDetail(ctx context.Context, soapAction string, envelope, request, response interface{}, faultDetail FaultError, attachments *[]MIMEMultipartAttachment) error { - return s.call(ctx, soapAction, request, response, faultDetail, attachments) + return s.call(ctx, soapAction, envelope, request, response, faultDetail, attachments) } // CallContextWithFault performs HTTP POST request. // Note that if SOAP fault is returned, it will be stored in the error. -func (s *Client) CallContextWithFaultDetail(ctx context.Context, soapAction string, request, response interface{}, faultDetail FaultError) error { - return s.call(ctx, soapAction, request, response, faultDetail, nil) +func (s *Client) CallContextWithFaultDetail(ctx context.Context, soapAction string, envelope, request, response interface{}, faultDetail FaultError) error { + return s.call(ctx, soapAction, envelope, request, response, faultDetail, nil) } // CallWithFaultDetail performs HTTP POST request. // Note that if SOAP fault is returned, it will be stored in the error. // the passed in fault detail is expected to implement FaultError interface, // which allows to condense the detail into a short error message. -func (s *Client) CallWithFaultDetail(soapAction string, request, response interface{}, faultDetail FaultError) error { - return s.call(context.Background(), soapAction, request, response, faultDetail, nil) +func (s *Client) CallWithFaultDetail(soapAction string, envelope, request, response interface{}, faultDetail FaultError) error { + return s.call(context.Background(), soapAction, envelope, request, response, faultDetail, nil) } -func (s *Client) call(ctx context.Context, soapAction string, request, response interface{}, faultDetail FaultError, +func (s *Client) call(ctx context.Context, soapAction string, envelope, request, response interface{}, faultDetail FaultError, retAttachments *[]MIMEMultipartAttachment) error { - // SOAP envelope capable of namespace prefixes - envelope := SOAPEnvelope{ - XmlNS: XmlNsSoapEnv, + if envelope == nil { + // SOAP envelope capable of namespace prefixes + soapEnvelope := SOAPEnvelope{ + XmlNS: XmlNsSoapEnv, + } + soapEnvelope.Headers = s.headers + soapEnvelope.Body.Content = request + envelope = soapEnvelope } - envelope.Headers = s.headers - - envelope.Body.Content = request buffer := new(bytes.Buffer) var encoder SOAPEncoder if s.opts.mtom && s.opts.mma { @@ -482,7 +483,7 @@ func (s *Client) call(ctx context.Context, soapAction string, request, response defer res.Body.Close() if res.StatusCode >= 400 && res.StatusCode != 500 { - body, _ := ioutil.ReadAll(res.Body) + body, _ := io.ReadAll(res.Body) return &HTTPError{ StatusCode: res.StatusCode, ResponseBody: body, diff --git a/soap/soap_test.go b/soap/soap_test.go index b436df54..2e3a58a9 100644 --- a/soap/soap_test.go +++ b/soap/soap_test.go @@ -5,7 +5,7 @@ import ( "context" "encoding/xml" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -69,7 +69,7 @@ func TestClient_Call(t *testing.T) { client := NewClient(ts.URL) req := &Ping{Request: &PingRequest{Message: "Hi"}} reply := &PingResponse{} - if err := client.Call("GetData", req, reply); err != nil { + if err := client.Call("GetData", nil, req, reply); err != nil { t.Fatalf("couln't call service: %v", err) } @@ -122,7 +122,7 @@ func TestClient_Send_Correct_Headers(t *testing.T) { client := NewClient(ts.URL, WithHTTPHeaders(test.reqHeaders)) req := struct{}{} reply := struct{}{} - client.Call(test.action, req, reply) + client.Call(test.action, nil, req, reply) for k, v := range test.expectedHeaders { h := gotHeaders.Get(k) @@ -138,7 +138,7 @@ func TestClient_Attachments_WithAttachmentResponse(t *testing.T) { for k, v := range r.Header { w.Header().Set(k, v[0]) } - bodyBuf, _ := ioutil.ReadAll(r.Body) + bodyBuf, _ := io.ReadAll(r.Body) _, err := w.Write(bodyBuf) if err != nil { panic(err) @@ -166,7 +166,7 @@ func TestClient_Attachments_WithAttachmentResponse(t *testing.T) { retAttachments := make([]MIMEMultipartAttachment, 0) // WHEN - if err := client.CallContextWithAttachmentsAndFaultDetail(context.TODO(), "''", req, + if err := client.CallContextWithAttachmentsAndFaultDetail(context.TODO(), "''", nil, req, reply, nil, &retAttachments); err != nil { t.Fatalf("couln't call service: %v", err) } @@ -183,7 +183,7 @@ func TestClient_MTOM(t *testing.T) { for k, v := range r.Header { w.Header().Set(k, v[0]) } - bodyBuf, _ := ioutil.ReadAll(r.Body) + bodyBuf, _ := io.ReadAll(r.Body) w.Write(bodyBuf) })) defer ts.Close() @@ -191,7 +191,7 @@ func TestClient_MTOM(t *testing.T) { client := NewClient(ts.URL, WithMTOM()) req := &PingRequest{Attachment: NewBinary([]byte("Attached data")).SetContentType("text/plain")} reply := &PingRequest{} - if err := client.Call("GetData", req, reply); err != nil { + if err := client.Call("GetData", nil, req, reply); err != nil { t.Fatalf("couln't call service: %v", err) } @@ -353,7 +353,7 @@ func Test_Client_FaultDefault(t *testing.T) { Item: tt.emptyFault, hasData: tt.hasData, } - if err := client.CallWithFaultDetail("GetData", req, &reply, &fault); err != nil { + if err := client.CallWithFaultDetail("GetData", nil, req, &reply, &fault); err != nil { assert.EqualError(t, err, faultErrString) assert.EqualValues(t, tt.fault, fault.Item) } else { @@ -824,7 +824,7 @@ func TestHTTPError(t *testing.T) { })) defer ts.Close() client := NewClient(ts.URL) - gotErr := client.Call("GetData", &Ping{}, &PingResponse{}) + gotErr := client.Call("GetData", nil, &Ping{}, &PingResponse{}) if test.wantErr { if gotErr == nil { t.Fatalf("Expected an error from call. Received none") From 05a3af1f80f79e5d5adf994e7e25320fcf8fc0b5 Mon Sep 17 00:00:00 2001 From: Yousuf Ahmed Date: Tue, 5 Mar 2024 13:02:35 -0500 Subject: [PATCH 2/5] backward compatible --- soap/soap.go | 25 +++++++++------- soap/soap_test.go | 74 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/soap/soap.go b/soap/soap.go index 56e2a1ea..50056f17 100644 --- a/soap/soap.go +++ b/soap/soap.go @@ -373,36 +373,41 @@ func (s *Client) SetHeaders(headers ...interface{}) { } // CallContext performs HTTP POST request with a context -func (s *Client) CallContext(ctx context.Context, soapAction string, envelope, request, response interface{}) error { - return s.call(ctx, soapAction, envelope, request, response, nil, nil) +func (s *Client) CallContext(ctx context.Context, soapAction string, request, response interface{}) error { + return s.call(ctx, soapAction, nil, request, response, nil, nil) } // Call performs HTTP POST request. // Note that if the server returns a status code >= 400, a HTTPError will be returned -func (s *Client) Call(soapAction string, envelope, request, response interface{}) error { - return s.call(context.Background(), soapAction, envelope, request, response, nil, nil) +func (s *Client) Call(soapAction string, request, response interface{}) error { + return s.call(context.Background(), soapAction, nil, request, response, nil, nil) } // CallContextWithAttachmentsAndFaultDetail performs HTTP POST request. // Note that if SOAP fault is returned, it will be stored in the error. // On top the attachments array will be filled with attachments returned from the SOAP request. -func (s *Client) CallContextWithAttachmentsAndFaultDetail(ctx context.Context, soapAction string, envelope, request, +func (s *Client) CallContextWithAttachmentsAndFaultDetail(ctx context.Context, soapAction string, request, response interface{}, faultDetail FaultError, attachments *[]MIMEMultipartAttachment) error { - return s.call(ctx, soapAction, envelope, request, response, faultDetail, attachments) + return s.call(ctx, soapAction, nil, request, response, faultDetail, attachments) } // CallContextWithFault performs HTTP POST request. // Note that if SOAP fault is returned, it will be stored in the error. -func (s *Client) CallContextWithFaultDetail(ctx context.Context, soapAction string, envelope, request, response interface{}, faultDetail FaultError) error { - return s.call(ctx, soapAction, envelope, request, response, faultDetail, nil) +func (s *Client) CallContextWithFaultDetail(ctx context.Context, soapAction string, request, response interface{}, faultDetail FaultError) error { + return s.call(ctx, soapAction, nil, request, response, faultDetail, nil) } // CallWithFaultDetail performs HTTP POST request. // Note that if SOAP fault is returned, it will be stored in the error. // the passed in fault detail is expected to implement FaultError interface, // which allows to condense the detail into a short error message. -func (s *Client) CallWithFaultDetail(soapAction string, envelope, request, response interface{}, faultDetail FaultError) error { - return s.call(context.Background(), soapAction, envelope, request, response, faultDetail, nil) +func (s *Client) CallWithFaultDetail(soapAction string, request, response interface{}, faultDetail FaultError) error { + return s.call(context.Background(), soapAction, nil, request, response, faultDetail, nil) +} + +func (s *Client) CallWithEnvelope(ctx context.Context, soapAction string, envelope, response interface{}, faultDetail FaultError, + retAttachments *[]MIMEMultipartAttachment) error { + return s.call(ctx, soapAction, envelope, nil, response, faultDetail, retAttachments) } func (s *Client) call(ctx context.Context, soapAction string, envelope, request, response interface{}, faultDetail FaultError, diff --git a/soap/soap_test.go b/soap/soap_test.go index 2e3a58a9..253c4707 100644 --- a/soap/soap_test.go +++ b/soap/soap_test.go @@ -48,32 +48,80 @@ type AttachmentRequest struct { ContentID string `xml:"contentID,omitempty"` } +type TestSoapRequest struct { + XMLName xml.Name + Body Body +} + +type Body struct { + XMLName xml.Name + PingRequest Ping `xml:"Ping"` +} + func TestClient_Call(t *testing.T) { - var pingRequest = new(Ping) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - xml.NewDecoder(r.Body).Decode(pingRequest) - rsp := ` + var soapRequest TestSoapRequest + err := xml.NewDecoder(r.Body).Decode(&soapRequest) + assert.NoError(t, err) + rsp := fmt.Sprintf(` - Pong hi + Pong %s - ` + `, soapRequest.Body.PingRequest.Request.Message) w.Write([]byte(rsp)) })) defer ts.Close() client := NewClient(ts.URL) - req := &Ping{Request: &PingRequest{Message: "Hi"}} + req := &Ping{Request: &PingRequest{Message: "Alex"}} + reply := &PingResponse{} + if err := client.Call("GetData", req, reply); err != nil { + t.Fatalf("couln't call service: %v", err) + } + + wantedMsg := "Pong Alex" + if reply.PingResult.Message != wantedMsg { + t.Errorf("got msg %s wanted %s", reply.PingResult.Message, wantedMsg) + } +} + +func TestClient_CallEnvelope(t *testing.T) { + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var soapRequest TestSoapRequest + err := xml.NewDecoder(r.Body).Decode(&soapRequest) + assert.NoError(t, err) + rsp := fmt.Sprintf(` + + + + + Pong %s + + + + `, soapRequest.Body.PingRequest.Request.Message) + w.Write([]byte(rsp)) + })) + defer ts.Close() + + client := NewClient(ts.URL) + req := &Ping{Request: &PingRequest{Message: "Bill"}} + var env TestSoapRequest + env.Body.PingRequest = *req + reply := &PingResponse{} - if err := client.Call("GetData", nil, req, reply); err != nil { + if err := client.CallWithEnvelope(context.Background(), "GetData", env, reply, nil, nil); err != nil { t.Fatalf("couln't call service: %v", err) } - wantedMsg := "Pong hi" + wantedMsg := "Pong Bill" if reply.PingResult.Message != wantedMsg { t.Errorf("got msg %s wanted %s", reply.PingResult.Message, wantedMsg) } @@ -122,7 +170,7 @@ func TestClient_Send_Correct_Headers(t *testing.T) { client := NewClient(ts.URL, WithHTTPHeaders(test.reqHeaders)) req := struct{}{} reply := struct{}{} - client.Call(test.action, nil, req, reply) + client.Call(test.action, req, reply) for k, v := range test.expectedHeaders { h := gotHeaders.Get(k) @@ -166,7 +214,7 @@ func TestClient_Attachments_WithAttachmentResponse(t *testing.T) { retAttachments := make([]MIMEMultipartAttachment, 0) // WHEN - if err := client.CallContextWithAttachmentsAndFaultDetail(context.TODO(), "''", nil, req, + if err := client.CallContextWithAttachmentsAndFaultDetail(context.TODO(), "''", req, reply, nil, &retAttachments); err != nil { t.Fatalf("couln't call service: %v", err) } @@ -191,7 +239,7 @@ func TestClient_MTOM(t *testing.T) { client := NewClient(ts.URL, WithMTOM()) req := &PingRequest{Attachment: NewBinary([]byte("Attached data")).SetContentType("text/plain")} reply := &PingRequest{} - if err := client.Call("GetData", nil, req, reply); err != nil { + if err := client.Call("GetData", req, reply); err != nil { t.Fatalf("couln't call service: %v", err) } @@ -353,7 +401,7 @@ func Test_Client_FaultDefault(t *testing.T) { Item: tt.emptyFault, hasData: tt.hasData, } - if err := client.CallWithFaultDetail("GetData", nil, req, &reply, &fault); err != nil { + if err := client.CallWithFaultDetail("GetData", req, &reply, &fault); err != nil { assert.EqualError(t, err, faultErrString) assert.EqualValues(t, tt.fault, fault.Item) } else { @@ -824,7 +872,7 @@ func TestHTTPError(t *testing.T) { })) defer ts.Close() client := NewClient(ts.URL) - gotErr := client.Call("GetData", nil, &Ping{}, &PingResponse{}) + gotErr := client.Call("GetData", &Ping{}, &PingResponse{}) if test.wantErr { if gotErr == nil { t.Fatalf("Expected an error from call. Received none") From 7745a325829a13dcea773aba36e419f8c1198003 Mon Sep 17 00:00:00 2001 From: Gregory Hunt Date: Tue, 5 Mar 2024 14:26:38 -0500 Subject: [PATCH 3/5] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b869588a..af72578a 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/hooklift/gowsdl +module github.com/ParticleHealth/gowsdl go 1.15 From df27ccdcf822f2c07499acac36da5b3c3d91e2b2 Mon Sep 17 00:00:00 2001 From: Yousuf Ahmed Date: Sat, 9 Mar 2024 15:35:17 -0500 Subject: [PATCH 4/5] update go.mod --- go.mod | 5 ++++- go.sum | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index af72578a..d0c1d00b 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/ParticleHealth/gowsdl go 1.15 -require github.com/stretchr/testify v1.6.1 +require ( + github.com/hooklift/gowsdl v0.5.0 // indirect + github.com/stretchr/testify v1.6.1 +) diff --git a/go.sum b/go.sum index afe7890c..5b643da6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hooklift/gowsdl v0.5.0 h1:DE8RevqhGPLchumV/V7OwbCzfJ8lcozFg1uWC/ESCBQ= +github.com/hooklift/gowsdl v0.5.0/go.mod h1:9kRc402w9Ci/Mek5a1DNgTmU14yPY8fMumxNVvxhis4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 386c8a24fa6b4aaf2d9021b62ab74e57769420d6 Mon Sep 17 00:00:00 2001 From: Yousuf Ahmed Date: Sat, 9 Mar 2024 15:37:32 -0500 Subject: [PATCH 5/5] allow call with response env --- example/server/example.go | 2 +- soap/soap.go | 101 +++++++++++++++++++++++++++++--------- soap/soap_test.go | 67 ++++++++++++++++++++++--- 3 files changed, 138 insertions(+), 32 deletions(-) diff --git a/example/server/example.go b/example/server/example.go index 3fd0f52b..20b9ffc8 100644 --- a/example/server/example.go +++ b/example/server/example.go @@ -5,7 +5,7 @@ import ( "net/http" "time" - "github.com/hooklift/gowsdl/example/server/gen" + "github.com/ParticleHealth/gowsdl/example/server/gen" "github.com/hooklift/gowsdl/soap" ) diff --git a/soap/soap.go b/soap/soap.go index 50056f17..69a1455a 100644 --- a/soap/soap.go +++ b/soap/soap.go @@ -12,6 +12,20 @@ import ( "time" ) +type SOAPResponseEnvelopeInterface interface { + GetBody() SoapResponseBodyInterface + GetHeader() interface{} + SetBody(body SoapResponseBodyInterface) + GetAttachments() []MIMEMultipartAttachment + SetHeader(interface{}) + SetXMLName(xml.Name) +} + +type SoapResponseBodyInterface interface { + ErrorFromFault() error + SetContent(interface{}) +} + type SOAPEncoder interface { Encode(v interface{}) error Flush() error @@ -24,10 +38,34 @@ type SOAPDecoder interface { type SOAPEnvelopeResponse struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` Header *SOAPHeaderResponse - Body SOAPBodyResponse + Body SoapResponseBodyInterface Attachments []MIMEMultipartAttachment `xml:"attachments,omitempty"` } +func (s *SOAPEnvelopeResponse) GetBody() SoapResponseBodyInterface { + return s.Body +} + +func (s *SOAPEnvelopeResponse) GetHeader() interface{} { + return s.Header +} + +func (s *SOAPEnvelopeResponse) SetBody(body SoapResponseBodyInterface) { + s.Body = body +} + +func (s *SOAPEnvelopeResponse) SetHeader(header interface{}) { + s.Header = header.(*SOAPHeaderResponse) +} + +func (s *SOAPEnvelopeResponse) SetXMLName(xmlName xml.Name) { + s.XMLName = xmlName +} + +func (s *SOAPEnvelopeResponse) GetAttachments() []MIMEMultipartAttachment { + return s.Attachments +} + type SOAPEnvelope struct { XMLName xml.Name `xml:"soap:Envelope"` XmlNS string `xml:"xmlns:soap,attr"` @@ -137,6 +175,10 @@ func (b *SOAPBodyResponse) ErrorFromFault() error { return nil } +func (b *SOAPBodyResponse) SetContent(content interface{}) { + b.Content = content +} + type DetailContainer struct { Detail interface{} } @@ -374,13 +416,13 @@ func (s *Client) SetHeaders(headers ...interface{}) { // CallContext performs HTTP POST request with a context func (s *Client) CallContext(ctx context.Context, soapAction string, request, response interface{}) error { - return s.call(ctx, soapAction, nil, request, response, nil, nil) + return s.call(ctx, soapAction, nil, request, response, nil, nil, nil) } // Call performs HTTP POST request. // Note that if the server returns a status code >= 400, a HTTPError will be returned func (s *Client) Call(soapAction string, request, response interface{}) error { - return s.call(context.Background(), soapAction, nil, request, response, nil, nil) + return s.call(context.Background(), soapAction, nil, request, response, nil, nil, nil) } // CallContextWithAttachmentsAndFaultDetail performs HTTP POST request. @@ -388,13 +430,13 @@ func (s *Client) Call(soapAction string, request, response interface{}) error { // On top the attachments array will be filled with attachments returned from the SOAP request. func (s *Client) CallContextWithAttachmentsAndFaultDetail(ctx context.Context, soapAction string, request, response interface{}, faultDetail FaultError, attachments *[]MIMEMultipartAttachment) error { - return s.call(ctx, soapAction, nil, request, response, faultDetail, attachments) + return s.call(ctx, soapAction, nil, request, response, nil, faultDetail, attachments) } // CallContextWithFault performs HTTP POST request. // Note that if SOAP fault is returned, it will be stored in the error. func (s *Client) CallContextWithFaultDetail(ctx context.Context, soapAction string, request, response interface{}, faultDetail FaultError) error { - return s.call(ctx, soapAction, nil, request, response, faultDetail, nil) + return s.call(ctx, soapAction, nil, request, response, nil, faultDetail, nil) } // CallWithFaultDetail performs HTTP POST request. @@ -402,24 +444,25 @@ func (s *Client) CallContextWithFaultDetail(ctx context.Context, soapAction stri // the passed in fault detail is expected to implement FaultError interface, // which allows to condense the detail into a short error message. func (s *Client) CallWithFaultDetail(soapAction string, request, response interface{}, faultDetail FaultError) error { - return s.call(context.Background(), soapAction, nil, request, response, faultDetail, nil) + return s.call(context.Background(), soapAction, nil, request, response, nil, faultDetail, nil) } -func (s *Client) CallWithEnvelope(ctx context.Context, soapAction string, envelope, response interface{}, faultDetail FaultError, +func (s *Client) CallWithEnvelope(ctx context.Context, soapAction string, requestEnvelope interface{}, responseEnvelope SOAPResponseEnvelopeInterface, faultDetail FaultError, retAttachments *[]MIMEMultipartAttachment) error { - return s.call(ctx, soapAction, envelope, nil, response, faultDetail, retAttachments) + return s.call(ctx, soapAction, requestEnvelope, nil, nil, responseEnvelope, faultDetail, retAttachments) } -func (s *Client) call(ctx context.Context, soapAction string, envelope, request, response interface{}, faultDetail FaultError, +func (s *Client) call(ctx context.Context, soapAction string, requestEnvelope, request, response interface{}, responseEnvelope SOAPResponseEnvelopeInterface, faultDetail FaultError, retAttachments *[]MIMEMultipartAttachment) error { - if envelope == nil { + if requestEnvelope == nil { // SOAP envelope capable of namespace prefixes soapEnvelope := SOAPEnvelope{ XmlNS: XmlNsSoapEnv, } soapEnvelope.Headers = s.headers soapEnvelope.Body.Content = request - envelope = soapEnvelope + requestEnvelope = soapEnvelope + } buffer := new(bytes.Buffer) @@ -434,7 +477,7 @@ func (s *Client) call(ctx context.Context, soapAction string, envelope, request, encoder = xml.NewEncoder(buffer) } - if err := encoder.Encode(envelope); err != nil { + if err := encoder.Encode(requestEnvelope); err != nil { return err } @@ -494,15 +537,25 @@ func (s *Client) call(ctx context.Context, soapAction string, envelope, request, ResponseBody: body, } } + if responseEnvelope == nil { + // xml Decoder (used with and without MTOM) cannot handle namespace prefixes (yet), + // so we have to use a namespace-less response envelope + // soapResponse := new(SOAPEnvelopeResponse) + // soapResponse.Body = SOAPBodyResponse{ + // Content: response, + // Fault: &SOAPFault{ + // Detail: faultDetail, + // }, + // } + responseEnvelope = &SOAPEnvelopeResponse{ + Body: &SOAPBodyResponse{ + Content: response, + Fault: &SOAPFault{ + Detail: faultDetail, + }, + }, + } - // xml Decoder (used with and without MTOM) cannot handle namespace prefixes (yet), - // so we have to use a namespace-less response envelope - respEnvelope := new(SOAPEnvelopeResponse) - respEnvelope.Body = SOAPBodyResponse{ - Content: response, - Fault: &SOAPFault{ - Detail: faultDetail, - }, } mtomBoundary, err := getMtomHeader(res.Header.Get("Content-Type")) @@ -539,7 +592,7 @@ func (s *Client) call(ctx context.Context, soapAction string, envelope, request, dec = xml.NewDecoder(body) } - if err := dec.Decode(respEnvelope); err != nil { + if err := dec.Decode(responseEnvelope); err != nil { // the response doesn't contain a Fault/SOAPBody, so we return the original body if res.StatusCode == 500 { return &HTTPError{ @@ -550,8 +603,8 @@ func (s *Client) call(ctx context.Context, soapAction string, envelope, request, return err } - if respEnvelope.Attachments != nil { - *retAttachments = respEnvelope.Attachments + if responseEnvelope.GetAttachments() != nil { + *retAttachments = responseEnvelope.GetAttachments() } - return respEnvelope.Body.ErrorFromFault() + return responseEnvelope.GetBody().ErrorFromFault() } diff --git a/soap/soap_test.go b/soap/soap_test.go index 253c4707..451f72e0 100644 --- a/soap/soap_test.go +++ b/soap/soap_test.go @@ -15,9 +15,12 @@ import ( "github.com/stretchr/testify/assert" ) -type Ping struct { - XMLName xml.Name `xml:"http://example.com/service.xsd Ping"` +// Ping -> PingRequest -> (Message, Attachment) + +// PingResponse -> PingReply -> (Message, Attachment) +type Ping struct { + XMLName xml.Name `xml:"http://example.com/service.xsd Ping"` Request *PingRequest `xml:"request,omitempty"` } @@ -29,11 +32,18 @@ type PingRequest struct { } type PingResponse struct { - XMLName xml.Name `xml:"http://example.com/service.xsd PingResponse"` - + XMLName xml.Name `xml:"http://example.com/service.xsd PingResponse"` PingResult *PingReply `xml:"PingResult,omitempty"` } +func (b *TestResponseBody) ErrorFromFault() error { + return nil +} + +func (b *TestResponseBody) SetContent(content interface{}) { + b.PingResponse = content.(*PingResponse) +} + type PingReply struct { // XMLName xml.Name `xml:"http://example.com/service.xsd PingReply"` @@ -50,14 +60,50 @@ type AttachmentRequest struct { type TestSoapRequest struct { XMLName xml.Name - Body Body + Body TestRequestBody } -type Body struct { +type TestRequestBody struct { XMLName xml.Name PingRequest Ping `xml:"Ping"` } +type TestResponseBody struct { + XMLName xml.Name + PingResponse *PingResponse `xml:"PingResponse"` +} + +type TestSoapResponse struct { + XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` + Header *SOAPHeaderResponse + Body SoapResponseBodyInterface `xml:"Body"` + Attachments []MIMEMultipartAttachment `xml:"attachments,omitempty"` +} + +func (s *TestSoapResponse) GetBody() SoapResponseBodyInterface { + return s.Body +} + +func (s *TestSoapResponse) GetHeader() interface{} { + return s.Header +} + +func (s *TestSoapResponse) SetBody(body SoapResponseBodyInterface) { + s.Body = body +} + +func (s *TestSoapResponse) SetHeader(header interface{}) { + s.Header = header.(*SOAPHeaderResponse) +} + +func (s *TestSoapResponse) SetXMLName(xmlName xml.Name) { + s.XMLName = xmlName +} + +func (s *TestSoapResponse) GetAttachments() []MIMEMultipartAttachment { + return s.Attachments +} + func TestClient_Call(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -117,7 +163,14 @@ func TestClient_CallEnvelope(t *testing.T) { env.Body.PingRequest = *req reply := &PingResponse{} - if err := client.CallWithEnvelope(context.Background(), "GetData", env, reply, nil, nil); err != nil { + responeBody := TestResponseBody{} + responeBody.SetContent(reply) + + respEnv := &TestSoapResponse{ + Body: &responeBody, + } + + if err := client.CallWithEnvelope(context.Background(), "GetData", env, respEnv, nil, nil); err != nil { t.Fatalf("couln't call service: %v", err) }