UNPKG

soap-graphql

Version:

Create a GraphQL schema from a WSDL-defined SOAP endpoint.

262 lines 13.5 kB
"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