strong-soap
Version:
A minimal node SOAP client
358 lines (354 loc) • 14.8 kB
JavaScript
// Copyright IBM Corp. 2016,2018. All Rights Reserved.
// Node module: strong-soap
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var g = require('../../globalize');
var WSDLElement = require('./wsdlElement');
var descriptor = require('../xsd/descriptor');
var ElementDescriptor = descriptor.ElementDescriptor;
var TypeDescriptor = descriptor.TypeDescriptor;
var QName = require('../qname');
var helper = require('../helper');
var SimpleType = require('../xsd/simpleType');
var assert = require('assert');
const Style = {
documentLiteralWrapped: 'documentLiteralWrapped',
documentLiteral: 'documentLiteral',
rpcLiteral: 'rpcLiteral',
rpcEncoded: 'rpcEncoded',
documentEncoded: 'documentEncoded'
};
class Operation extends WSDLElement {
constructor(nsName, attrs, options) {
super(nsName, attrs, options);
//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 = [];
this.soapVersion;
}
addChild(child) {
switch (child.name) {
case 'input':
this.input = child;
break;
case 'output':
this.output = child;
break;
case 'fault':
this.faults.push(child);
break;
case 'operation':
// soap:operation
this.soapAction = child.$soapAction || '';
this.style = child.$style || '';
this.soapActionRequired = child.$soapActionRequired === 'true' || child.$soapActionRequired === '1' || false;
//figure out from the binding operation soap version 1.1 or 1.2
if (child.nsURI === 'http://schemas.xmlsoap.org/wsdl/soap/') {
this.soapVersion = '1.1';
} else if (child.nsURI === 'http://schemas.xmlsoap.org/wsdl/soap12/') {
this.soapVersion = '1.2';
} else {
this.soapVersion = '1.1';
}
break;
}
}
postProcess(definitions) {
try {
if (this._processed) return; // Already processed
if (this.input) this.input.postProcess(definitions);
if (this.output) this.output.postProcess(definitions);
for (let i = 0, n = this.faults.length; i < n; i++) {
this.faults[i].postProcess(definitions);
}
if (this.parent.name === 'binding') {
this.getMode();
}
this._processed = true;
} catch (err) {
throw err;
}
}
static describeHeaders(param, definitions) {
if (param == null) return null;
var headers = new descriptor.TypeDescriptor();
if (!param.headers) return headers;
param.headers.forEach(function (header) {
var part = header.part;
if (part && part.element) {
headers.addElement(part.element.describe(definitions));
} else if (part && part.type) {
g.warn('{{WS-I}} violation: ' + '{{http://ws-i.org/profiles/basicprofile-1.2-2010-11-09.html#BP2113}}' + ' part %s', part.$name);
}
});
return headers;
}
describeFaults(definitions) {
var faults = {};
for (var f in this.faults) {
let fault = this.faults[f];
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 {
g.warn('{{WS-I}} violation: ' + '{{http://ws-i.org/profiles/basicprofile-1.2-2010-11-09.html#BP2113}}' + ' part %s', part.$name);
}
}
return faults;
}
describe(definitions) {
if (this.descriptor) return this.descriptor;
var input, output;
switch (this.mode) {
case Style.documentLiteralWrapped:
if (this.input && this.input.body) {
for (let p in this.input.body.parts) {
let wrapperElement = this.input.body.parts[p].element;
if (wrapperElement) {
input = wrapperElement.describe(definitions);
}
break;
}
}
if (this.output && this.output.body) {
for (let p in this.output.body.parts) {
let wrapperElement = this.output.body.parts[p].element;
if (wrapperElement) {
output = wrapperElement.describe(definitions);
}
break;
}
}
break;
case Style.documentLiteral:
input = new descriptor.TypeDescriptor();
output = new descriptor.TypeDescriptor();
if (this.input && this.input.body) {
for (let p in this.input.body.parts) {
let element = this.input.body.parts[p].element;
if (element) {
input.addElement(element.describe(definitions));
}
}
}
if (this.output && this.output.body) {
for (let p in this.output.body.parts) {
let element = this.output.body.parts[p].element;
if (element) {
output.addElement(element.describe(definitions));
}
}
}
break;
case Style.rpcLiteral:
case Style.rpcEncoded:
// The operation wrapper element
let nsURI = this.input && this.input.body && this.input.body.namespace || this.targetNamespace;
input = new descriptor.ElementDescriptor(new QName(nsURI, this.$name), null, 'qualified', false);
output = new descriptor.ElementDescriptor(new QName(nsURI, this.$name + 'Response'), null, 'qualified', false);
let inputParts = new descriptor.TypeDescriptor();
let outputParts = new descriptor.TypeDescriptor();
if (this.input && this.input.body) {
for (let p in this.input.body.parts) {
let part = this.input.body.parts[p];
let type;
if (part.type) {
if (part.type instanceof SimpleType) {
var qName = new QName(part.type.targetNamespace, part.type.$name, part.type.prefix);
type = qName;
} else {
type = part.type.qname;
}
let element = new descriptor.ElementDescriptor(new QName(nsURI, p), type, 'unqualified', false);
inputParts.addElement(element);
} else if (part.element) {
var elementDescriptor = part.element.describe(definitions);
inputParts.addElement(elementDescriptor);
}
}
}
if (this.output && this.output.body) {
for (let p in this.output.body.parts) {
let part = this.output.body.parts[p];
let type;
if (part.type) {
if (part.type instanceof SimpleType) {
var qName = new QName(part.type.targetNamespace, part.type.$name, part.type.prefix);
type = qName;
} else {
type = part.type.qname;
}
let element = new descriptor.ElementDescriptor(new QName(nsURI, p), type, 'unqualified', false);
outputParts.addElement(element);
} else if (part.element) {
let elementDescriptor = part.element.describe(definitions);
outputParts.addElement(elementDescriptor);
}
}
}
input.elements = inputParts.elements;
output.elements = outputParts.elements;
break;
case Style.documentEncoded:
throw new Error(g.f('{{WSDL}} style not supported: %s', Style.documentEncoded));
}
let faults = this.describeFaults(definitions);
let inputHeaders = Operation.describeHeaders(this.input, definitions);
let outputHeaders = Operation.describeHeaders(this.output, definitions);
this.descriptor = {
name: this.$name,
style: this.mode,
soapAction: this.soapAction,
soapVersion: this.soapVersion,
input: {
body: input,
headers: inputHeaders
},
output: {
body: output,
headers: outputHeaders
},
faults: {
body: {
Fault: {
faults
}
}
}
};
this.descriptor.inputEnvelope = Operation.createEnvelopeDescriptor(this.descriptor.input, false, this.soapVersion);
this.descriptor.outputEnvelope = Operation.createEnvelopeDescriptor(this.descriptor.output, true, this.soapVersion);
this.descriptor.faultEnvelope = Operation.createEnvelopeDescriptor(this.descriptor.faults, true, this.soapVersion);
return this.descriptor;
}
static createEnvelopeDescriptor(parameterDescriptor, isOutput, soapVersion, prefix, nsURI) {
prefix = prefix || 'soap';
var soapNsURI;
if (soapVersion === '1.1') {
soapNsURI = 'http://schemas.xmlsoap.org/soap/envelope/';
} else if (soapVersion === '1.2') {
soapNsURI = 'http://www.w3.org/2003/05/soap-envelope';
}
nsURI = nsURI || soapNsURI;
var descriptor = new TypeDescriptor();
var envelopeDescriptor = new ElementDescriptor(new QName(nsURI, 'Envelope', prefix), null, 'qualified', false);
descriptor.add(envelopeDescriptor);
var headerDescriptor = new ElementDescriptor(new QName(nsURI, 'Header', prefix), null, 'qualified', false);
var bodyDescriptor = new ElementDescriptor(new QName(nsURI, 'Body', prefix), null, 'qualified', false);
envelopeDescriptor.addElement(headerDescriptor);
envelopeDescriptor.addElement(bodyDescriptor);
//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);
}
//process faults. An example of resulting structure of the <Body> element with soap 1.1 <Fault> element descriptor:
/*
<soap:Body>
<soap:Fault>
<faultcode>sampleFaultCode</faultcode>
<faultstring>sampleFaultString</faultstring>
<detail>
<ns1:myMethodFault1 xmlns:ns1="http://example.com/doc_literal_wrapped_test.wsdl">
<errorMessage1>MyMethod Business Exception message</errorMessage1>
<value1>10</value1>
</ns1:myMethodFault1>
</detail>
</soap:Fault>
</soap:Body>
*/
if (isOutput && parameterDescriptor && parameterDescriptor.body.Fault) {
let xsdStr = new QName(helper.namespaces.xsd, 'string', 'xsd');
var form;
if (soapVersion === '1.1') {
form = 'unqualified';
} else if (soapVersion === '1.2') {
form = 'qualified';
}
let faultDescriptor = new ElementDescriptor(new QName(nsURI, 'Fault', prefix), null, 'qualified', false);
bodyDescriptor.add(faultDescriptor);
let detailDescriptor;
if (soapVersion === '1.1') {
faultDescriptor.add(new ElementDescriptor(new QName(nsURI, 'faultcode', prefix), null, form, false));
faultDescriptor.add(new ElementDescriptor(new QName(nsURI, 'faultstring', prefix), null, form, false));
faultDescriptor.add(new ElementDescriptor(new QName(nsURI, 'faultactor', prefix), null, form, false));
detailDescriptor = new ElementDescriptor(new QName(nsURI, 'detail', prefix), null, form, false);
faultDescriptor.add(detailDescriptor);
} else if (soapVersion === '1.2') {
let code = new ElementDescriptor(new QName(nsURI, 'Code', prefix));
code.add(new ElementDescriptor(new QName(nsURI, 'Value', prefix), null, form, false));
let subCode = new ElementDescriptor(new QName(nsURI, 'Subcode', prefix), null, form, false);
code.add(subCode);
subCode.add(new ElementDescriptor(new QName(nsURI, 'Value', prefix), null, form, false));
faultDescriptor.add(code, null, form, false);
let reason = new ElementDescriptor(new QName(nsURI, 'Reason', prefix));
reason.add(new ElementDescriptor(new QName(nsURI, 'Text', prefix), null, form, false));
faultDescriptor.add(reason, null, form, false);
faultDescriptor.add(new ElementDescriptor(new QName(nsURI, 'Node', prefix), null, form, false));
faultDescriptor.add(new ElementDescriptor(new QName(nsURI, 'Role', prefix), null, form, false));
detailDescriptor = new ElementDescriptor(new QName(nsURI, 'Detail', prefix), null, form, false);
faultDescriptor.add(detailDescriptor);
}
//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]);
}
}
return descriptor;
}
getMode() {
let use = this.input && this.input.body && this.input.body.use || 'literal';
if (this.style === 'document' && use === 'literal') {
// document literal
let element = null;
let count = 0;
if (this.input && this.input.body) {
for (let p in this.input.body.parts) {
let part = this.input.body.parts[p];
element = part.element;
if (!(part.element && !part.type)) {
console.error('Document/literal part should use element', part);
throw new Error('Document/literal part should use element');
}
count++;
}
}
// Only one part and the input wrapper element has the same name as
// operation
if (count === 1 && element.$name === this.$name) {
count = 0;
if (this.output && this.output.body) {
for (let p in this.output.body.parts) {
let part = this.output.body.parts[p];
element = part.element;
assert(part.element && !part.type, 'Document/literal part should use element');
count++;
}
}
if (count === 1) {
this.mode = Style.documentLiteralWrapped;
} else {
this.mode = Style.documentLiteral;
}
} else {
this.mode = Style.documentLiteral;
}
} else if (this.style === 'document' && use === 'encoded') {
this.mode = Style.documentEncoded;
} else if (this.style === 'rpc' && use === 'encoded') {
this.mode = Style.rpcEncoded;
} else if (this.style === 'rpc' && use === 'literal') {
this.mode = Style.rpcLiteral;
}
return this.mode;
}
}
Operation.Style = Style;
Operation.elementName = 'operation';
Operation.allowedChildren = ['documentation', 'input', 'output', 'fault', 'operation'];
module.exports = Operation;
//# sourceMappingURL=operation.js.map