Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed support for Fault, added support for multiple faults & new tests #9

Merged
merged 1 commit into from
Sep 13, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/client.js
Original file line number Diff line number Diff line change
@@ -209,7 +209,14 @@ class Client extends Base {
callback(err);
} else {

var outputEnvDescriptor = operationDescriptor.outputEnvelope;
//figure out if this is a Fault response or normal output from the server.
//There seem to be no good way to figure this out other than
//checking for <Fault> element in server response.
if (body.indexOf('<soap:Fault>') > -1 || body.indexOf('<Fault>') > -1) {
var outputEnvDescriptor = operationDescriptor.faultEnvelope;
} else {
var outputEnvDescriptor = operationDescriptor.outputEnvelope;
}
try {
obj = xmlHandler.xmlToJson(nsContext, body, outputEnvDescriptor);
} catch (error) {
@@ -223,6 +230,7 @@ class Client extends Base {
return callback(null, response, json);
}
}
//Reaches here for Fault processing as well since Fault is thrown as an error in xmlHandler.xmlToJson(..) function.
error.response = response;
error.body = body;
self.emit('soapError', error);
7 changes: 4 additions & 3 deletions src/parser/wsdl/binding.js
Original file line number Diff line number Diff line change
@@ -43,10 +43,11 @@ class Binding extends WSDLElement {
if (operation.output && child.output) {
child.output.message = operation.output.message;
}
// Set portType.operation.fault.message to binding.operation.fault
for (var f in child.faults) {

//portType.operation.fault is fully processed with message etc. Hence set to binding.operation.fault
for (var f in operation.faults) {
if (operation.faults[f]) {
child.faults[f].message = operation.faults[f].message;
child.faults[f] = operation.faults[f];
}
}
if (operation.$parameterOrder) {
63 changes: 50 additions & 13 deletions src/parser/wsdl/operation.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ var descriptor = require('../xsd/descriptor');
var ElementDescriptor = descriptor.ElementDescriptor;
var TypeDescriptor = descriptor.TypeDescriptor;
var QName = require('../qname');
var helper = require('../helper');

var assert = require('assert');

@@ -17,7 +18,9 @@ const Style = {
class Operation extends WSDLElement {
constructor(nsName, attrs, options) {
super(nsName, attrs, options);
this.faults = {};
//there can be multiple faults defined in the operation. They all will have same type name 'fault'
//what differentiates them from each other is, the element/s which will get added under fault <detail> during runtime.
this.faults = [];
}

addChild(child) {
@@ -29,7 +32,7 @@ class Operation extends WSDLElement {
this.output = child;
break;
case 'fault':
this.faults[child.$name] = child;
this.faults.push(child);
break;
case 'operation': // soap:operation
this.soapAction = child.$soapAction || '';
@@ -72,7 +75,7 @@ class Operation extends WSDLElement {
var faults = {};
for (var f in this.faults) {
let fault = this.faults[f];
let part = fault.fault && fault.fault.part;
let part = fault.message && fault.message.children[0]; //find the part through Fault message. There is only one part in fault message
if (part && part.element) {
faults[f] = part.element.describe(definitions);
} else {
@@ -190,12 +193,17 @@ class Operation extends WSDLElement {
body: output,
headers: outputHeaders
},
faults: this.faults
faults: {
body: {Fault : {faults}}
}
};
this.descriptor.inputEnvelope =
Operation.createEnvelopeDescriptor(this.descriptor.input, false);
this.descriptor.outputEnvelope =
Operation.createEnvelopeDescriptor(this.descriptor.output, true);
this.descriptor.faultEnvelope =
Operation.createEnvelopeDescriptor(this.descriptor.faults, true);

return this.descriptor;
}

@@ -217,30 +225,59 @@ class Operation extends WSDLElement {
envelopeDescriptor.addElement(headerDescriptor);
envelopeDescriptor.addElement(bodyDescriptor);

if (parameterDescriptor && parameterDescriptor.body) {
//add only if input or output. Fault is list of faults unlike input/output element and fault needs further processing below,
//before it can be added to the <body>
if (parameterDescriptor && parameterDescriptor.body && !parameterDescriptor.body.Fault) {
bodyDescriptor.add(parameterDescriptor.body);
}

if (parameterDescriptor && parameterDescriptor.headers) {
bodyDescriptor.add(parameterDescriptor.headers);
}

if (isOutput && parameterDescriptor && parameterDescriptor.faults) {
//process faults. An example of resulting structure of the <Body> element with <Fault> element descriptor:
/*
<Body>
<Fault>
<faultcode> </faultcode>
<faultstring> </faultstring>
<faultactor> </faultactor>
<detail>
<myMethodFault1>
<errorMessage1> </errorMessage1>
<value1> </value1>
</myMethodFault1>
</detail>
<detail>
<myMethodFault2>
<errorMessage2> </errorMessage2>
<value2> </value2>
</myMethodFault2>
</detail>
</Fault>
</Body>
*/
if (isOutput && parameterDescriptor && parameterDescriptor.body.Fault) {
let xsdStr = new QName(helper.namespaces.xsd, 'string', 'xsd');
let faultDescriptor = new ElementDescriptor(
new QName(nsURI, 'Fault', prefix), null, 'qualified', false);
bodyDescriptor.add(faultDescriptor);
faultDescriptor.add(
new ElementDescriptor(nsURI, 'faultcode', xsdStr, 'qualified', false));
new ElementDescriptor(new QName(nsURI, 'faultcode', prefix), null, 'qualified', false));
faultDescriptor.add(
new ElementDescriptor(nsURI, 'faultstring', xsdStr, 'qualified', false));
new ElementDescriptor(new QName(nsURI, 'faultstring', prefix), null, 'qualified', false));
faultDescriptor.add(
new ElementDescriptor(nsURI, 'faultactor', xsdStr, 'qualified', false));
new ElementDescriptor(new QName(nsURI, 'faultactor', prefix), null, 'qualified', false));
let detailDescriptor =
new ElementDescriptor(nsURI, 'detail', null, 'qualified', false);
faultDescriptor.add(detailDescriptor);
new ElementDescriptor(new QName(nsURI, 'detail', prefix), null, 'qualified', false);

for (let f in parameterDescriptor.faults) {
detailDescriptor.add(parameterDescriptor.faults[f]);
//multiple faults may be defined in wsdl for this operation. Go though every Fault and add it under <detail> element.
for (var f in parameterDescriptor.body.Fault.faults) {
detailDescriptor.add(parameterDescriptor.body.Fault.faults[f]);
}
//only add <detail> element to <Fault> descriptor only if there is more than one <detail> element
if (detailDescriptor.elements.length > 0) {
faultDescriptor.add(detailDescriptor);
}
}

6 changes: 4 additions & 2 deletions src/parser/wsdl/parameter.js
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ class Parameter extends WSDLElement {
this.headers = this.headers || [];
// soap:header
this.headers.push(child);
} else if (child.name === 'fault') {
} else if (child.name === 'fault') { //Revisit. Never gets executed.
this.fault = child;
}
}
@@ -68,7 +68,9 @@ class Parameter extends WSDLElement {
}
}
}
if (this.fault) {
//Revisit.. this.name is always undefined because there is no code which calls addChild(..) with child.name = 'fault.
//code works inspite of not executing this block. Remove it?
if (this.name === 'fault') {
let message = this.fault.parent.message;
if (message) {
message.postProcess(definitions);
32 changes: 22 additions & 10 deletions src/server.js
Original file line number Diff line number Diff line change
@@ -245,9 +245,9 @@ class Server extends Base {
}
} catch (error) {
if (error.Fault !== undefined) {
return self._sendError(error, callback, includeTimestamp);
return self._sendError(operations[name], error, callback, includeTimestamp);
}

//Revisit - is this needed?
throw error;
}
};
@@ -278,15 +278,18 @@ class Server extends Base {
return;
handled = true;

var operation = self.wsdl.definitions.services[serviceName]
.ports[portName].binding.operations[operationName];


if (error && error.Fault !== undefined) {
return self._sendError(error, callback, includeTimestamp);
return self._sendError(operation, error, callback, includeTimestamp);
}
else if (result === undefined) {
// Backward compatibility to support one argument callback style
result = error;
}
var operation = self.wsdl.definitions.services[serviceName]
.ports[portName].binding.operations[operationName];

var element = operation.output;

var operationDescriptor = operation.describe(self.wsdl.definitions);
@@ -361,7 +364,7 @@ class Server extends Base {
return env;
};

_sendError(error, callback, includeTimestamp) {
_sendError(operation, error, callback, includeTimestamp) {
var self = this,
fault;

@@ -372,11 +375,14 @@ class Server extends Base {
}

var env = XMLHandler.createSOAPEnvelope();
var operationDescriptor = operation.describe(this.wsdl.definitions);
//get envelope descriptor
var faultEnvDescriptor = operation.descriptor.faultEnvelope.elements[0];
var soapNsURI = 'http://schemas.xmlsoap.org/soap/envelope/';
//revisit this logic. It doesn't seem to make any difference whether prefix is 'soap' or null

if (error.Fault.faultcode) {
// Soap 1.1 error style
// Root element will be prependend with the soap NS
// Root element will be prepended with the soap NS
// It must match the NS defined in the Envelope (set by the _envelope method)
var nsContext = self.createNamespaceContext('soap', soapNsURI);
}
@@ -386,8 +392,14 @@ class Server extends Base {
// It must match the NS defined in the Envelope (set by the _envelope method)
var nsContext = self.createNamespaceContext(null, soapNsURI);
}
//jsonToXML needs the outer wrapper object which in this case is error in order to extract serialize Fault into the soap body
this.xmlHandler.jsonToXml(env.body, nsContext, null, error);
//find the envelope body descriptor
var bodyDescriptor = faultEnvDescriptor.elements[1];

//there will be only one <Fault> element descriptor under <Body>
var faultDescriptor = bodyDescriptor.elements[0];

//serialize Fault object into XML as per faultDescriptor
this.xmlHandler.jsonToXml(env.body, nsContext, faultDescriptor, error.Fault);

self._envelope(env, includeTimestamp);
var message = env.body.toString({pretty: true});
338 changes: 290 additions & 48 deletions test/server-client-document-test.js

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions test/server-test.js
Original file line number Diff line number Diff line change
@@ -370,7 +370,7 @@ describe('SOAP Server', function() {
it('should return SOAP Fault body for SOAP 1.2', function(done) {
soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) {
assert.ok(!err);
var expectedBody = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Header/>\n <soap:Body>\n <Fault>\n <Code>\n <Value>soap:Sender</Value>\n <Subcode>\n <value>rpc:BadArguments</value>\n </Subcode>\n </Code>\n <Reason>\n <Text>Processing Error</Text>\n </Reason>\n </Fault>\n </soap:Body>\n</soap:Envelope>';
var expectedBody = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Header/>\n <soap:Body>\n <soap:Fault>\n <Code>\n <Value>soap:Sender</Value>\n <Subcode>\n <value>rpc:BadArguments</value>\n </Subcode>\n </Code>\n <Reason>\n <Text>Processing Error</Text>\n </Reason>\n </soap:Fault>\n </soap:Body>\n</soap:Envelope>';
client.GetLastTradePrice({ TradePriceRequest: {tickerSymbol: 'SOAP Fault v1.2' }}, function(err, response, body) {
assert.ok(err);
var fault = err.root.Envelope.Body.Fault;
@@ -394,9 +394,11 @@ describe('SOAP Server', function() {
assert.equal(fault.faultcode, "soap:Client.BadArguments");
assert.equal(fault.faultstring, "Error while processing arguments");
// Verify namespace on elements set according to fault spec 1.1
assert.ok(body.match(/<faultcode>.*<\/faultcode>/g),
//revisit soap: namespace for below elements. Current code either can add soap: for <Fault> including every child element
//under <Fault> or NOT add soap: for <Fault> including every child element under <Fault>.
assert.ok(body.match(/<soap:faultcode>.*<\/soap:faultcode>/g),
"Body should contain faultcode-element without namespace");
assert.ok(body.match(/<faultstring>.*<\/faultstring>/g),
assert.ok(body.match(/<soap:faultstring>.*<\/soap:faultstring>/g),
"Body should contain faultstring-element without namespace");
done();
});
32 changes: 32 additions & 0 deletions test/wsdl/strict/doc_literal_wrapped_test.wsdl
Original file line number Diff line number Diff line change
@@ -32,6 +32,25 @@
</xsd:complexType>
</xsd:element>

<!--Fault element -->
<xsd:element name="myMethodFault1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="errorMessage1" type="xsd:string"/>
<xsd:element name="value1" type="xsd:int"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>

<xsd:element name="myMethodFault2">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="errorMessage2" type="xsd:string"/>
<xsd:element name="value2" type="xsd:int"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>

</xsd:schema>
</wsdl:types>

@@ -43,10 +62,20 @@
<wsdl:part name="parameters" element="xsd1:myMethodResponse"/>
</wsdl:message>

<wsdl:message name="myMethodFault1">
<wsdl:part name="parameters" element="xsd1:myMethodFault1"/>
</wsdl:message>

<wsdl:message name="myMethodFault2">
<wsdl:part name="parameters" element="xsd1:myMethodFault2"/>
</wsdl:message>

<wsdl:portType name="DocLiteralWrappedPortType">
<wsdl:operation name="myMethod">
<wsdl:input message="tns:myMethodRequest"/>
<wsdl:output message="tns:myMethodResponse"/>
<wsdl:fault message="tns:myMethodFault1"/>
<wsdl:fault message="tns:myMethodFault2"/>
</wsdl:operation>
</wsdl:portType>

@@ -61,6 +90,9 @@
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault>
<soap:body use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>