soap-graphql
Version:
Create a GraphQL schema from a WSDL-defined SOAP endpoint.
262 lines • 13.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var util_1 = require("util");
var NodeSoapWsdlResolver = /** @class */ (function () {
function NodeSoapWsdlResolver(wsdl, logger) {
this.wsdl = wsdl;
this.logger = logger;
this.alreadyResolved = new Map();
}
NodeSoapWsdlResolver.prototype.warn = function (message) {
this.logger.warn(message);
};
NodeSoapWsdlResolver.prototype.debug = function (message) {
this.logger.debug(message);
};
NodeSoapWsdlResolver.prototype.createOperationArgs = function (operation) {
var _this = this;
var inputContent = operation.content()['input'];
this.debug(function () { return "creating args for operation '" + operation.name() + "' from content '" + util_1.inspect(inputContent, false, 5) + "'"; });
if (!inputContent) {
this.warn(function () { return "no input definition for operation '" + operation.name() + "'"; });
}
// inputContent===null -> argNames===[]
var argNames = nonNamespaceKeys(inputContent);
var inputNamespace = inputContent && targetNamespace(inputContent);
var args = argNames.map(function (key) {
return _this.createOperationArg(operation, inputNamespace, key, inputContent[key]);
}).filter(function (arg) { return !!arg; });
return args;
};
NodeSoapWsdlResolver.prototype.createOperationArg = function (operation, inputNamespace, argWsdlFieldName, argContent) {
this.debug(function () { return "creating arg for operation '" + operation.name() + "' from content '" + util_1.inspect(argContent, false, 5) + "'"; });
var parsedArgName = parseWsdlFieldName(argWsdlFieldName);
var inputType = this.resolveContentToSoapType(inputNamespace, argContent, "arg '" + argWsdlFieldName + "' of operation '" + operation.name() + "'");
var input = {
name: parsedArgName.name,
type: inputType,
isList: parsedArgName.isList,
};
return input;
};
NodeSoapWsdlResolver.prototype.createOperationOutput = function (operation) {
var outputContent = operation.content()['output'];
this.debug(function () { return "creating output for operation '" + operation.name() + "' from content '" + util_1.inspect(outputContent, false, 5) + "'"; });
// determine type and field name
var resultType;
var resultFieldName;
var outputNamespace = targetNamespace(outputContent);
var ownerStringForLog = "output of operation '" + operation.name() + "'";
if (!outputContent) {
this.warn(function () { return "no definition for output type of operation '" + operation.name() + "', using 'string'"; });
resultType = this.resolveContentToSoapType(outputNamespace, 'string', ownerStringForLog);
}
else {
var outputContentKeys = nonNamespaceKeys(outputContent);
if (outputContentKeys.length <= 0) {
// content has no sub content
// void operation; use String as result type. when executed, it will return null
resultFieldName = null;
resultType = this.resolveContentToSoapType(outputNamespace, 'string', ownerStringForLog);
}
else {
if (outputContentKeys.length > 1) {
// content has multiple fields, use the first one
// @todo maybe better build an extra type for this case, but how to name it?
this.warn(function () { return "multiple result fields in output definition of operation '" + operation.name() + "', using first one"; });
}
resultFieldName = outputContentKeys[0];
var resultContent = outputContent[resultFieldName];
if (!resultContent) {
this.warn(function () { return "no type definition for result field '" + resultFieldName + "' in output definition for operation '" + operation.name() + "', using 'string'"; });
resultType = this.resolveContentToSoapType(outputNamespace, 'string', ownerStringForLog);
}
else {
resultType = this.resolveContentToSoapType(outputNamespace, resultContent, ownerStringForLog);
}
}
}
var parsedResultFieldName = parseWsdlFieldName(resultFieldName);
return {
type: {
type: resultType,
isList: parsedResultFieldName.isList
},
resultField: parsedResultFieldName.name
};
};
NodeSoapWsdlResolver.prototype.resolveContentToSoapType = function (parentNamespace, typeContent, ownerStringForLog) {
this.debug(function () { return "resolving soap type for " + ownerStringForLog + " from content '" + util_1.inspect(typeContent, false, 3) + "'"; });
// determine name of the type
var wsdlTypeName;
var namespace;
if (!typeContent) {
this.warn(function () { return "no type definition for " + ownerStringForLog + ", using 'string'"; });
wsdlTypeName = 'string';
namespace = parentNamespace;
}
else if (typeof typeContent === 'string') {
// primitive type
wsdlTypeName = withoutNamespace(typeContent);
namespace = parentNamespace;
}
else {
wsdlTypeName = this.findTypeName(typeContent);
if (!wsdlTypeName) {
this.warn(function () { return "no type name found for " + ownerStringForLog + ", using 'string'"; });
wsdlTypeName = 'string';
}
namespace = targetNamespace(typeContent);
}
return this.resolveWsdlNameToSoapType(namespace, wsdlTypeName, ownerStringForLog);
};
NodeSoapWsdlResolver.prototype.findTypeName = function (content) {
var types = this.wsdl.definitions.descriptions.types;
for (var key in types) {
if (types[key] === content) {
return key;
}
}
return null;
};
NodeSoapWsdlResolver.prototype.resolveWsdlNameToSoapType = function (namespace, wsdlTypeName, ownerStringForLog) {
this.debug(function () { return "resolving soap type for " + ownerStringForLog + " from namespace '" + namespace + "', type name '" + wsdlTypeName + "'"; });
// lookup cache; this accomplishes three things:
// 1) an incredible boost in performance, must be at least 3ns, !!hax0r!!11
// 2) every type definition (primitive and complex) has only one instance of SoapType
// 3) resolve circular dependencies between types
if (this.alreadyResolved.has(namespace + wsdlTypeName)) {
this.debug(function () { return "resolved soap type for namespace: '" + namespace + "', typeName: '" + wsdlTypeName + "' from cache"; });
return this.alreadyResolved.get(namespace + wsdlTypeName);
}
// get the defition of the type from the schema section in the WSDL
var xsdTypeDefinition = this.findXsdTypeDefinition(namespace, wsdlTypeName);
if (!xsdTypeDefinition) {
// has no type definition
// --> primitive type, e.g. 'string'
var soapType_1 = wsdlTypeName;
this.alreadyResolved.set(namespace + wsdlTypeName, soapType_1);
this.debug(function () { return "resolved namespace: '" + namespace + "', typeName: '" + wsdlTypeName + "' to primitive type '" + soapType_1 + "'"; });
return soapType_1;
}
else {
// create a new object type
var soapType_2 = {
name: xsdTypeDefinition.$name,
base: null,
fields: null,
};
this.alreadyResolved.set(namespace + wsdlTypeName, soapType_2);
// resolve bindings (field types, base type) after type has been registered to resolve circular dependencies
this.resolveTypeBody(soapType_2, namespace, xsdTypeDefinition);
this.debug(function () { return "resolved namespace: '" + namespace + "', typeName: '" + wsdlTypeName + "' to object type '" + util_1.inspect(soapType_2, false, 3) + "'"; });
return soapType_2;
}
};
NodeSoapWsdlResolver.prototype.resolveAnonymousTypeToSoapType = function (xsdFieldDefinition, parentSoapType) {
var namespace = xsdFieldDefinition.$targetNamespace;
var ownerStringForLog = "field '" + xsdFieldDefinition.$name + "' of soap type '" + parentSoapType.name + "'";
this.debug(function () { return "resolving anonymous type for " + ownerStringForLog + " from namespace '" + namespace + "'"; });
var generatedTypeName = parentSoapType.name + "_" + capitalizeFirstLetter(xsdFieldDefinition.$name);
while (!!this.findXsdTypeDefinition(namespace, generatedTypeName)) {
generatedTypeName = generatedTypeName + "_";
}
var soapType = {
name: generatedTypeName,
base: null,
fields: null,
};
// resolve bindings (field types, base type)
var bodyTypeDefinition = xsdFieldDefinition.children ? xsdFieldDefinition.children[0] : undefined;
if (bodyTypeDefinition) {
this.resolveTypeBody(soapType, namespace, bodyTypeDefinition);
this.debug(function () { return "resolved namespace: '" + namespace + "', typeName: '" + generatedTypeName + "' to object type '" + util_1.inspect(soapType, false, 3) + "'"; });
}
else {
this.warn(function () { return "cannot determine type definition for soap type '" + generatedTypeName + "', leaving fields empty"; });
}
return soapType;
};
NodeSoapWsdlResolver.prototype.findXsdTypeDefinition = function (namespace, typeName) {
return this.wsdl.findSchemaObject(namespace, typeName);
};
NodeSoapWsdlResolver.prototype.resolveTypeBody = function (soapType, namespace, typeDefinition) {
var _this = this;
this.debug(function () { return "resolving body of soap type '" + soapType.name + "' from namespace '" + namespace + "', definition '" + util_1.inspect(typeDefinition, false, 9) + "'"; });
var typeName = typeDefinition.$name;
var fields = null;
var baseTypeName = null;
var body = typeDefinition.children[0];
if (body.name === 'sequence') {
var sequence = body;
fields = sequence.children || [];
}
else if (body.name === 'complexContent') {
var extension = body.children[0];
var sequence = extension.children[0];
baseTypeName = withoutNamespace(extension.$base);
fields = sequence.children || [];
}
else {
this.warn(function () { return "cannot parse fields for soap type '" + typeName + "', leaving fields empty"; });
fields = [];
}
var soapFields = fields.map(function (field) {
var type;
if (field.$type) {
type = _this.resolveWsdlNameToSoapType(field.$targetNamespace, withoutNamespace(field.$type), "field '" + field.$name + "' of soap type '" + soapType.name + "'");
}
else {
type = _this.resolveAnonymousTypeToSoapType(field, soapType);
}
return {
name: field.$name,
type: type,
isList: !!field.$maxOccurs && field.$maxOccurs === 'unbounded'
};
});
// @todo in XSD it is possible to inherit a type from a primitive ... may have to handle this
var baseType = !baseTypeName ? null : this.resolveWsdlNameToSoapType(namespace, baseTypeName, "base type of soap type '" + soapType.name + "'");
soapType.fields = soapFields;
soapType.base = baseType;
};
return NodeSoapWsdlResolver;
}());
exports.NodeSoapWsdlResolver = NodeSoapWsdlResolver;
function nonNamespaceKeys(obj) {
return !obj ? [] : Object.keys(obj).filter(function (key) { return !isNamespaceKey(key); });
}
function targetNamespace(content) {
return content['targetNamespace'];
}
function isNamespaceKey(key) {
return key === 'targetNSAlias' || key === 'targetNamespace';
}
function withoutNamespace(value) {
if (!value) {
return value;
}
var matcher = value.match(/[a-zA-Z0-9]+\:(.+)/);
return !matcher || matcher.length < 2 ? value : matcher[1];
}
function isWsdlListFieldName(wsdlFieldName) {
return !!wsdlFieldName && wsdlFieldName.endsWith('[]');
}
function parseWsdlFieldName(wsdlFieldName) {
if (isWsdlListFieldName(wsdlFieldName)) {
return {
name: wsdlFieldName.substring(0, wsdlFieldName.length - 2),
isList: true,
};
}
else {
return {
name: wsdlFieldName,
isList: false,
};
}
}
function capitalizeFirstLetter(value) {
return value.charAt(0).toUpperCase() + value.substring(1);
}
//# sourceMappingURL=node-soap-resolver.js.map