@graphql-mesh/soap
Version:
1,064 lines (1,050 loc) • 41.4 kB
JavaScript
;
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
const soap = _interopDefault(require('soap'));
const graphql = require('graphql');
const graphqlScalars = require('graphql-scalars');
const crossHelpers = require('@graphql-mesh/cross-helpers');
const utils = require('@graphql-mesh/utils');
const store = require('@graphql-mesh/store');
async function createSoapClient(url, options = {}) {
const opts = !options.options ? {} : options.options;
return new Promise((resolve, reject) => {
try {
soap.createClient(url, opts, (err, client) => {
if (err) {
reject(err);
}
else {
if (options.basicAuth) {
client.setSecurity(new soap.BasicAuthSecurity(options.basicAuth.username, options.basicAuth.password));
}
resolve(client);
}
});
}
catch (err) {
reject(err);
}
});
}
const defaultOutputNameResolver = (soapType) => {
return !soapType ? null : !soapType.name ? null : capitalizeFirstLetter(soapType.name);
};
const defaultInputNameResolver = (soapType) => {
return !soapType ? null : !soapType.name ? null : capitalizeFirstLetter(soapType.name) + 'Input';
};
const defaultInterfaceNameResolver = (soapType) => {
return !soapType ? null : !soapType.name ? null : 'i' + capitalizeFirstLetter(soapType.name);
};
function capitalizeFirstLetter(value) {
return value.charAt(0).toUpperCase() + value.substring(1);
}
/**
* Default implementation of CustomTypeResolver.
* Based on https://www.w3.org/TR/xmlschema-2/#built-in-datatypes
*/
class DefaultTypeResolver {
constructor() {
this.string = graphql.GraphQLString;
this.base64Binary = graphqlScalars.GraphQLByte;
this.hexBinary = graphqlScalars.GraphQLHexadecimal;
this.duration = graphqlScalars.GraphQLDuration;
this.gYearMonth = graphql.GraphQLString;
this.gYear = graphql.GraphQLString;
this.gMonthDay = graphql.GraphQLString;
this.gDay = graphql.GraphQLString;
this.gMonth = graphql.GraphQLString;
this.anyURI = graphqlScalars.GraphQLURL;
this.QName = graphql.GraphQLString;
this.normalizedString = graphql.GraphQLString;
this.token = graphql.GraphQLString;
this.NMTOKEN = graphql.GraphQLString;
this.NMTOKENS = graphql.GraphQLString;
this.language = graphql.GraphQLString;
this.Name = graphql.GraphQLString;
this.NCName = graphql.GraphQLString;
this.IDREF = graphql.GraphQLID;
this.IDREFS = new graphql.GraphQLList(graphql.GraphQLID);
this.ENTITY = graphql.GraphQLID;
this.ENTITIES = new graphql.GraphQLList(graphql.GraphQLID);
this.ID = graphql.GraphQLID;
this.boolean = graphql.GraphQLBoolean;
this.byte = graphqlScalars.GraphQLByte;
this.unsignedByte = graphqlScalars.GraphQLByte;
this.short = graphql.GraphQLInt;
this.unsignedShort = graphqlScalars.GraphQLNonNegativeInt;
this.int = graphql.GraphQLInt;
this.unsignedInt = graphqlScalars.GraphQLNonNegativeInt;
this.integer = graphql.GraphQLInt;
this.positiveInteger = graphqlScalars.GraphQLPositiveInt;
this.nonPositiveInteger = graphqlScalars.GraphQLNonPositiveInt;
this.negativeInteger = graphqlScalars.GraphQLNegativeInt;
this.nonNegativeInteger = graphqlScalars.GraphQLNonNegativeInt;
this.long = graphqlScalars.GraphQLBigInt;
this.unsignedLong = graphqlScalars.GraphQLBigInt;
this.decimal = graphql.GraphQLFloat;
this.float = graphql.GraphQLFloat;
this.double = graphql.GraphQLFloat;
this.dateTime = graphqlScalars.GraphQLDateTime;
this.date = graphqlScalars.GraphQLDate;
this.time = graphqlScalars.GraphQLTime;
}
resolve(typeName) {
return this[typeName];
}
outputType(typeName) {
return this.resolve(typeName);
}
inputType(typeName) {
return this.resolve(typeName);
}
}
class SchemaResolver {
constructor(soapEndpoint, soapCaller, options, logger) {
this.soapEndpoint = soapEndpoint;
this.soapCaller = soapCaller;
this.logger = logger;
this.outputResolver = null;
this.inputResolver = null;
this.options = this.defaultOptions(options);
}
defaultOptions(options) {
options = !options ? {} : Object.assign({}, options);
if (!options.outputNameResolver) {
options.outputNameResolver = defaultOutputNameResolver;
}
if (!options.interfaceNameResolver) {
options.interfaceNameResolver = defaultInterfaceNameResolver;
}
if (!options.inputNameResolver) {
options.inputNameResolver = defaultInputNameResolver;
}
if (!options.customResolver) {
options.customResolver = new DefaultTypeResolver();
}
return options;
}
resolve() {
this.outputResolver = new GraphqlOutputFieldResolver(this.options, this.logger);
this.inputResolver = new GraphqlInputFieldResolver(this.options, this.logger);
return {
query: this.createQueryObject(),
mutation: this.createMutationObject(),
};
}
getFields(rootType) {
const fields = rootType === 'query'
? {
description: {
type: graphql.GraphQLString,
resolve: () => {
return this.soapEndpoint.description();
},
},
}
: {};
this.soapEndpoint.services().forEach((service) => {
if (this.options.includeServices) {
const fieldName = service.name();
fields[fieldName] = this.createSoapServiceField(service, rootType);
}
else if (this.options.includePorts) {
service.ports().forEach((port) => {
const fieldName = port.name();
fields[fieldName] = this.createSoapPortField(service, port, rootType);
});
}
else {
service.ports().forEach((port) => {
port.operations().forEach((operation) => {
const fieldConfig = this.createSoapOperationField(operation, rootType);
if (fieldConfig) {
fields[operation.name()] = fieldConfig;
}
});
});
}
});
return fields;
}
createQueryObject() {
return new graphql.GraphQLObjectType({
name: 'Query',
fields: () => this.getFields('query'),
});
}
createMutationObject() {
return new graphql.GraphQLObjectType({
name: 'Mutation',
fields: () => this.getFields('mutation'),
});
}
createSoapServiceField(service, rootType) {
const fieldsThunk = () => {
const fields = {};
service.ports().forEach((port) => {
if (this.options.includePorts) {
fields[port.name()] = this.createSoapPortField(service, port, rootType);
}
else {
port.operations().forEach((operation) => {
const fieldConfig = this.createSoapOperationField(operation, rootType);
if (fieldConfig) {
fields[operation.name()] = fieldConfig;
}
});
}
});
return fields;
};
const returnType = new graphql.GraphQLObjectType({
name: service.name() + 'Service' + (rootType === 'query' ? 'Query' : ''),
description: `Service ${service.name()}`,
fields: fieldsThunk,
});
return {
type: returnType,
description: `Service ${service.name()}`,
resolve: () => {
return {};
},
};
}
createSoapPortField(service, port, rootType) {
const fieldsThunk = () => {
const fields = {};
port.operations().forEach((operation) => {
const fieldConfig = this.createSoapOperationField(operation, rootType);
if (fieldConfig) {
fields[operation.name()] = fieldConfig;
}
});
return fields;
};
const returnType = new graphql.GraphQLObjectType({
name: port.name() + 'Port' + (rootType === 'query' ? 'Query' : ''),
description: `Port ${port.name()}, service ${service.name()}`,
fields: fieldsThunk,
});
return {
type: returnType,
description: `Port ${port.name()}, service ${service.name()}`,
resolve: () => {
return {};
},
};
}
getFieldConfig(operation) {
const args = this.createSoapOperationFieldArgs(operation);
const returnType = this.resolveSoapOperationReturnType(operation);
const resolver = this.createSoapOperationFieldResolver(operation);
return {
type: returnType,
description: `Operation ${operation.name()}, port ${operation.port().name()}, service ${operation
.service()
.name()}`,
args,
resolve: resolver,
};
}
createSoapOperationField(operation, rootType) {
var _a;
if ((_a = this.options.selectQueryOrMutationField) === null || _a === void 0 ? void 0 : _a.length) {
const selectionConfig = this.options.selectQueryOrMutationField.find(configElem => configElem.service === operation.service().name() &&
configElem.port === operation.port().name() &&
configElem.operation === operation.name());
if (selectionConfig != null) {
if (selectionConfig.type === rootType) {
return this.getFieldConfig(operation);
}
else {
return undefined;
}
}
}
if (this.options.selectQueryOperationsAuto) {
if (operation.name().toLowerCase().startsWith('get') ||
operation.name().toLowerCase().startsWith('find') ||
operation.name().toLowerCase().startsWith('list') ||
operation.name().toLowerCase().startsWith('query') ||
operation.name().toLowerCase().startsWith('search')) {
if (rootType === 'query') {
return this.getFieldConfig(operation);
}
}
else {
if (rootType === 'mutation') {
return this.getFieldConfig(operation);
}
}
}
else if (rootType === 'mutation') {
return this.getFieldConfig(operation);
}
return undefined;
}
createSoapOperationFieldArgs(operation) {
const args = {};
operation.args().forEach((soapField) => {
args[soapField.name] = {
type: this.inputResolver.resolve(soapField),
};
});
return args;
}
resolveSoapOperationReturnType(operation) {
return this.outputResolver.resolve(operation.output());
}
createSoapOperationFieldResolver(operation) {
return async (graphqlSource, graphqlArgs, graphqlContext, graphqlInfo) => {
return this.soapCaller.call({
operation,
graphqlSource,
graphqlArgs,
graphqlContext,
graphqlInfo,
});
};
}
}
class GraphqlOutputFieldResolver {
constructor(options, logger) {
this.options = options;
this.logger = logger;
this.alreadyResolvedOutputTypes = new Map();
this.alreadyResolvedInterfaceTypes = new Map();
}
resolve(input) {
try {
const type = this.resolveOutputType(input.type);
return input.isList ? new graphql.GraphQLList(type) : type;
}
catch (err) {
const errStacked = new Error(`could not resolve output type for ${crossHelpers.util.inspect(input)}`);
errStacked.stack += '\nCaused by: ' + err.stack;
throw errStacked;
}
}
resolveOutputType(soapType) {
var _a;
if (this.alreadyResolvedOutputTypes.has(soapType)) {
return this.alreadyResolvedOutputTypes.get(soapType);
}
if (typeof soapType === 'string') {
const customType = this.options.customResolver.outputType(soapType);
if (customType) {
this.alreadyResolvedOutputTypes.set(soapType, customType);
return customType;
}
}
else if ((soapType === null || soapType === void 0 ? void 0 : soapType.name) && ((_a = soapType === null || soapType === void 0 ? void 0 : soapType.fields) === null || _a === void 0 ? void 0 : _a.length) > 0) {
const objectType = this.createObjectType(soapType);
if (objectType) {
this.alreadyResolvedOutputTypes.set(soapType, objectType);
return objectType;
}
}
this.logger.warn(`could not resolve output type '`, soapType, `'; using GraphQLJSON instead`);
this.alreadyResolvedOutputTypes.set(soapType, graphqlScalars.GraphQLJSON);
return graphqlScalars.GraphQLJSON;
}
createObjectType(soapType) {
return new graphql.GraphQLObjectType(this.createObjectTypeConfig(soapType));
}
createObjectTypeConfig(soapType) {
const fields = () => {
const fieldMap = {};
this.appendObjectTypeFields(fieldMap, soapType);
return fieldMap;
};
const interfaces = () => {
const interfaces = [];
this.appendInterfaces(interfaces, soapType);
return interfaces;
};
return {
name: this.options.outputNameResolver(soapType),
fields,
interfaces,
};
}
appendObjectTypeFields(fieldMap, soapType) {
soapType.fields.forEach((soapField) => {
fieldMap[soapField.name] = {
type: this.resolve(soapField),
};
});
if (soapType.base) {
this.appendObjectTypeFields(fieldMap, soapType.base);
}
}
appendInterfaces(interfaces, soapType) {
if (soapType.base) {
interfaces.push(this.resolveInterfaceType(soapType.base));
this.appendInterfaces(interfaces, soapType.base);
}
}
resolveInterfaceType(soapType) {
if (this.alreadyResolvedInterfaceTypes.has(soapType)) {
return this.alreadyResolvedInterfaceTypes.get(soapType);
}
const interfaceType = this.createInterfaceType(soapType);
this.alreadyResolvedInterfaceTypes.set(soapType, interfaceType);
return interfaceType;
}
createInterfaceType(soapType) {
return new graphql.GraphQLInterfaceType(this.createInterfaceTypeConfig(soapType));
}
createInterfaceTypeConfig(soapType) {
const fields = () => {
const fieldMap = {};
this.appendInterfaceTypeFields(fieldMap, soapType);
return fieldMap;
};
return {
name: this.options.interfaceNameResolver(soapType),
fields,
// should never be called, since the schema will not contain ambigous return types
resolveType: (value, context, info) => {
throw Error('no interface resolving available');
},
};
}
appendInterfaceTypeFields(fieldMap, soapType) {
soapType.fields.forEach((soapField) => {
fieldMap[soapField.name] = {
type: this.resolve(soapField),
};
});
if (soapType.base) {
this.appendObjectTypeFields(fieldMap, soapType.base);
}
}
}
class GraphqlInputFieldResolver {
constructor(options, logger) {
this.options = options;
this.logger = logger;
this.alreadyResolved = new Map();
}
resolve(input) {
try {
const type = this.resolveInputType(input.type);
return input.isList ? new graphql.GraphQLList(type) : type;
}
catch (err) {
const errStacked = new Error(`could not resolve output type for ${crossHelpers.util.inspect(input)}`);
errStacked.stack += '\nCaused by: ' + err.stack;
throw errStacked;
}
}
resolveInputType(soapType) {
var _a;
if (this.alreadyResolved.has(soapType)) {
return this.alreadyResolved.get(soapType);
}
if (typeof soapType === 'string') {
const customType = this.options.customResolver.inputType(soapType);
if (customType) {
this.alreadyResolved.set(soapType, customType);
return customType;
}
}
else if ((soapType === null || soapType === void 0 ? void 0 : soapType.name) && ((_a = soapType === null || soapType === void 0 ? void 0 : soapType.fields) === null || _a === void 0 ? void 0 : _a.length) > 0) {
const objectType = this.createObjectType(soapType);
if (objectType) {
this.alreadyResolved.set(soapType, objectType);
return objectType;
}
}
this.logger.warn(`could not resolve input type '${soapType}'; using GraphQLString`);
this.alreadyResolved.set(soapType, graphql.GraphQLString);
return graphql.GraphQLString;
}
createObjectType(soapType) {
return new graphql.GraphQLInputObjectType(this.createObjectTypeConfig(soapType));
}
createObjectTypeConfig(soapType) {
const fields = () => {
const fieldMap = {};
this.appendObjectTypeFields(fieldMap, soapType);
return fieldMap;
};
return {
name: this.options.inputNameResolver(soapType),
fields,
};
}
appendObjectTypeFields(fieldMap, soapType) {
soapType.fields.forEach((soapField) => {
fieldMap[soapField.name] = {
type: this.resolve(soapField),
};
});
if (soapType.base) {
this.appendObjectTypeFields(fieldMap, soapType.base);
}
}
}
function createSchemaConfig(endpoint, soapCaller, options = {}, logger) {
return new SchemaResolver(endpoint, soapCaller, options, logger).resolve();
}
class NodeSoapWsdlResolver {
constructor(wsdl, logger) {
this.wsdl = wsdl;
this.logger = logger;
this.alreadyResolved = new Map();
}
warn(...args) {
this.logger.warn(...args);
}
debug(...args) {
this.logger.debug(...args);
}
createOperationArgs(operation) {
const inputContent = operation.content().input;
this.debug(`creating args for operation '${operation.name()}' from content`, inputContent);
if (!inputContent) {
this.warn(`no input definition for operation '${operation.name()}'`);
}
// inputContent===null -> argNames===[]
const argNames = nonNamespaceKeys(inputContent);
const inputNamespace = inputContent && targetNamespace(inputContent);
const args = argNames
.map((key) => {
return this.createOperationArg(operation, inputNamespace, key, inputContent[key]);
})
.filter(arg => !!arg);
return args;
}
createOperationArg(operation, inputNamespace, argWsdlFieldName, argContent) {
this.debug(`creating arg for operation '${operation.name()}' from content `, argContent);
const parsedArgName = parseWsdlFieldName(argWsdlFieldName);
const inputType = this.resolveContentToSoapType(inputNamespace, argContent, `arg '${argWsdlFieldName}' of operation '${operation.name()}'`);
const input = {
name: parsedArgName.name,
type: inputType,
isList: parsedArgName.isList,
};
return input;
}
createOperationOutput(operation) {
const outputContent = operation.content().output;
this.debug(`creating output for operation '${operation.name()}' from content `, outputContent);
// determine type and field name
let resultType;
let resultFieldName;
const outputNamespace = targetNamespace(outputContent);
const ownerStringForLog = `output of operation '${operation.name()}'`;
if (!outputContent) {
this.warn(`no definition for output type of operation '${operation.name()}', using 'string'`);
resultType = this.resolveContentToSoapType(outputNamespace, 'string', ownerStringForLog);
}
else {
const 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(`multiple result fields in output definition of operation '${operation.name()}', using first one`);
}
resultFieldName = outputContentKeys[0];
const resultContent = outputContent[resultFieldName];
if (!resultContent) {
this.warn(`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);
}
}
}
const parsedResultFieldName = parseWsdlFieldName(resultFieldName);
return {
type: {
type: resultType,
isList: parsedResultFieldName.isList,
},
resultField: parsedResultFieldName.name,
};
}
resolveContentToSoapType(parentNamespace, typeContent, ownerStringForLog) {
this.debug(`resolving soap type for ${ownerStringForLog} from content `, typeContent);
// determine name of the type
let wsdlTypeName;
let namespace;
if (!typeContent) {
this.warn(`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(`no type name found for ${ownerStringForLog}, using 'string'`);
wsdlTypeName = 'string';
}
namespace = targetNamespace(typeContent);
}
return this.resolveWsdlNameToSoapType(namespace, wsdlTypeName, ownerStringForLog);
}
findTypeName(content) {
const types = this.wsdl.definitions.descriptions.types;
for (const key in types) {
if (types[key] === content) {
return key;
}
}
return null;
}
resolveWsdlNameToSoapType(namespace, wsdlTypeName, ownerStringForLog) {
var _a;
this.debug(() => `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(`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
const xsdTypeDefinition = this.findXsdTypeDefinition(namespace, wsdlTypeName);
if (!((_a = xsdTypeDefinition === null || xsdTypeDefinition === void 0 ? void 0 : xsdTypeDefinition.children) === null || _a === void 0 ? void 0 : _a.length)) {
// has no type definition
// --> primitive type, e.g. 'string'
const soapType = wsdlTypeName;
this.alreadyResolved.set(namespace + wsdlTypeName, soapType);
this.debug(() => `resolved namespace: '${namespace}', typeName: '${wsdlTypeName}' to primitive type '${soapType}'`);
return soapType;
}
else {
// create a new object type
const soapType = {
name: xsdTypeDefinition.$name,
base: null,
fields: null,
};
this.alreadyResolved.set(namespace + wsdlTypeName, soapType);
// resolve bindings (field types, base type) after type has been registered to resolve circular dependencies
this.resolveTypeBody(soapType, namespace, xsdTypeDefinition);
this.debug(`resolved namespace: '${namespace}', typeName: '${wsdlTypeName}' to object type `, soapType);
return soapType;
}
}
findXsdTypeDefinition(namespace, typeName) {
return this.wsdl.findSchemaObject(namespace, typeName);
}
resolveTypeBody(soapType, namespace, typeDefinition) {
this.debug(`resolving body of soap type '${soapType.name}' from namespace '${namespace}', definition`, typeDefinition);
const typeName = typeDefinition.$name;
let fields = null;
let baseTypeName = null;
const body = typeDefinition.children[0];
if (body.name === 'sequence') {
const sequence = body;
fields = sequence.children || [];
}
else if (body.name === 'complexContent') {
const extension = body.children[0];
const sequence = extension.children[0];
baseTypeName = withoutNamespace(extension.$base);
fields = sequence.children || [];
}
else {
this.warn(`cannot parse fields for soap type '${typeName}', leaving fields empty`);
fields = [];
}
const soapFields = fields
.filter(field => field.$name)
.map((field) => {
return {
name: field.$name,
type: this.resolveWsdlNameToSoapType(field.$targetNamespace, withoutNamespace(field.$type), `field '${field.$name}' of soap type '${soapType.name}'`),
isList: !!field.$maxOccurs && field.$maxOccurs === 'unbounded',
};
});
// @todo in XSD it is possible to inherit a type from a primitive ... may have to handle this
const baseType = !baseTypeName
? null
: (this.resolveWsdlNameToSoapType(namespace, baseTypeName, `base type of soap type '${soapType.name}'`));
soapType.fields = soapFields;
soapType.base = baseType;
}
}
function nonNamespaceKeys(obj) {
return !obj ? [] : Object.keys(obj).filter(key => !isNamespaceKey(key.toString()));
}
function targetNamespace(content) {
return content.targetNamespace;
}
function isNamespaceKey(key) {
return key === 'targetNSAlias' || key === 'targetNamespace';
}
function withoutNamespace(value) {
if (!value) {
return value;
}
const 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 createSoapEndpoint(soapClient, logger) {
return new NodeSoapEndpoint(soapClient, logger);
}
class NodeSoapEndpoint {
constructor(soapClient, logger) {
this.soapClient = soapClient;
this._describe = null;
this._resolver = new NodeSoapWsdlResolver(this.soapClient.wsdl, logger);
}
description() {
return this.soapClient.wsdl.toXML();
}
services() {
const services = [];
const content = this.describe();
for (const key in content) {
services.push(new NodeSoapService(this, key, content[key]));
}
return services;
}
resolver() {
return this._resolver;
}
describe() {
if (!this._describe) {
this._describe = this.soapClient.describe();
}
return this._describe;
}
}
class NodeSoapService {
constructor(_wsdl, _name, _content) {
this._wsdl = _wsdl;
this._name = _name;
this._content = _content;
this._ports = null;
}
endpoint() {
return this._wsdl;
}
name() {
return this._name;
}
ports() {
if (!this._ports) {
this._ports = this.createPorts();
}
return this._ports;
}
createPorts() {
const ports = [];
for (const key in this._content) {
ports.push(new NodeSoapPort(this, key, this._content[key]));
}
return ports;
}
}
class NodeSoapPort {
constructor(_service, _name, _content) {
this._service = _service;
this._name = _name;
this._content = _content;
this._operations = null;
}
endpoint() {
return this.service().endpoint();
}
service() {
return this._service;
}
name() {
return this._name;
}
operations() {
if (!this._operations) {
this._operations = this.createOperations();
}
return this._operations;
}
createOperations() {
const operations = [];
for (const key in this._content) {
operations.push(new NodeSoapOperation(this, key, this._content[key]));
}
return operations;
}
}
class NodeSoapOperation {
constructor(_port, _name, _content) {
this._port = _port;
this._name = _name;
this._content = _content;
this._inputs = null;
this._output = null;
}
endpoint() {
return this.port().endpoint();
}
service() {
return this.port().service();
}
port() {
return this._port;
}
name() {
return this._name;
}
content() {
return this._content;
}
args() {
if (!this._inputs) {
this._inputs = this.endpoint().resolver().createOperationArgs(this);
}
return this._inputs;
}
output() {
if (!this._output) {
this._output = this.createOutput();
}
return this._output.type;
}
resultField() {
if (!this._output) {
this._output = this.createOutput();
}
return this._output.resultField;
}
createOutput() {
return this.endpoint().resolver().createOperationOutput(this);
}
}
/**
* Default implementation of SoapCaller for node-soap.
*/
class NodeSoapCaller {
constructor(soapClient, logger) {
this.soapClient = soapClient;
this.logger = logger;
}
async call(input) {
this.debug(() => [`call operation '${input.operation.name()}' with args '`, input.graphqlArgs]);
const requestFunction = crossHelpers.util.promisify(this.requestFunctionForOperation(input.operation).bind(this));
const requestMessage = await this.createSoapRequestMessage(input);
const res = await requestFunction(requestMessage);
return this.createGraphqlResult(input, res);
}
requestFunctionForOperation(operation) {
return this.soapClient[operation.service().name()][operation.port().name()][operation.name()];
}
async createSoapRequestMessage(input) {
const requestMessage = {};
Array.from(Object.keys(input.graphqlArgs)).forEach(key => {
// objects provided by GraphQL will usually lack default-functions like "hasOwnProperty"
// so deep-copy all objects to ensure those functions are present
requestMessage[key] = this.deepCopy(input.graphqlArgs[key]);
});
return requestMessage;
}
deepCopy(obj) {
if (!obj) {
return null;
}
else if (Object(obj) !== obj) {
// primitive
return obj;
}
else if (Array.isArray(obj)) {
return obj.map(e => this.deepCopy(e));
}
else {
const corrected = Object.assign({}, obj);
Array.from(Object.keys(corrected)).forEach(key => {
const value = corrected[key];
corrected[key] = this.deepCopy(value);
});
return corrected;
}
}
async createGraphqlResult(input, result) {
this.debug(() => [`operation '${input.operation.name()}' returned `, result]);
if (!input.operation.resultField()) {
// void operation
return !result ? null : JSON.stringify(result);
}
else {
return !result ? null : result[input.operation.resultField()];
}
}
debug(message) {
this.logger.debug(message);
}
}
/**
* Creates a GraphQL schema for the WSDL defined by the given parameters.
*
* The created GraphQL schema will include:
* - A Mutation-field for every operation in the WSDL.
* If the field is queried via GraphQL, the SOAP endpoint declared in the WSDL will be called and the result of the call will be returned via GraphQL.
* - A GraphQL output type for every WSDL type that is: a) used as a result of an operation and b) declared in the schema section of the WSDL.
* - A GraphQL interface type for every WSDL type that is: a) used as a base type of another type and b) declared in the schema section of the WSDL.
* - A GraphQL input type for every WSDL type that is: a) used as a input type of an operation and b) declared in the schema section of the WSDL.
* - A Query-field that returns the content of the WSDL (this is necessary, since a GraphQL schema must include at least one Query-field)
*
* @param options either an instance of SoapGraphQLOptions or the URL (http/https or path to a file) to a WSDL.
*/
async function soapGraphqlSchema(options) {
return new graphql.GraphQLSchema(await soapGraphqlSchemaConfig(options));
}
async function soapGraphqlSchemaConfig(options) {
const soapClient = await useSoapClient(options);
const wsdl = await createSoapEndpoint(soapClient, options.logger);
if (!options.soapCaller) {
options.soapCaller = new NodeSoapCaller(soapClient, options.logger);
}
return createSchemaConfig(wsdl, options.soapCaller, options.schemaOptions, options.logger);
}
async function useSoapClient(options) {
if (options.soapClient) {
return options.soapClient;
}
if (options.createClient) {
return createSoapClient(options.createClient.url, options.createClient.options);
}
throw new Error('neither soap client nor node-soap creation options provided');
}
class SoapHandler {
constructor({ config, baseDir, fetchFn, store: store$1, importFn, logger }) {
this.config = config;
this.baseDir = baseDir;
this.fetchFn = fetchFn;
this.wsdlResponse = store$1.proxy('wsdlResponse.json', store.PredefinedProxyOptions.JsonWithoutValidation);
this.importFn = importFn;
this.logger = logger;
}
async getMeshSource() {
let schemaHeaders = typeof this.config.schemaHeaders === 'string'
? await utils.loadFromModuleExportExpression(this.config.schemaHeaders, {
cwd: this.baseDir,
defaultExportName: 'default',
importFn: this.importFn,
})
: this.config.schemaHeaders;
if (typeof schemaHeaders === 'function') {
schemaHeaders = schemaHeaders();
}
if (schemaHeaders && 'then' in schemaHeaders) {
schemaHeaders = await schemaHeaders;
}
const soapClient = await createSoapClient(this.config.wsdl, {
basicAuth: this.config.basicAuth,
options: {
request: (async (requestObj) => {
const isWsdlRequest = requestObj.url === this.config.wsdl;
const sendRequest = async () => {
const headers = {
...requestObj.headers,
...(isWsdlRequest ? schemaHeaders : this.config.operationHeaders),
};
delete headers.Connection;
const res = await this.fetchFn(requestObj.url, {
headers,
method: requestObj.method,
body: requestObj.data,
});
const data = await res.text();
return {
data,
status: res.status,
statusText: res.statusText,
headers: utils.getHeadersObj(res.headers),
config: requestObj,
};
};
if (isWsdlRequest) {
return this.wsdlResponse.getWithSet(() => sendRequest());
}
return sendRequest();
}),
},
});
if (this.config.securityCert) {
const securityCertConfig = this.config.securityCert;
const [privateKey, publicKey, password] = await Promise.all([
securityCertConfig.privateKey ||
(securityCertConfig.privateKeyPath &&
utils.readFileOrUrl(securityCertConfig.privateKeyPath, {
allowUnknownExtensions: true,
cwd: this.baseDir,
importFn: this.importFn,
fetch: this.fetchFn,
logger: this.logger,
})),
securityCertConfig.publicKey ||
(securityCertConfig.publicKeyPath &&
utils.readFileOrUrl(securityCertConfig.publicKeyPath, {
allowUnknownExtensions: true,
cwd: this.baseDir,
importFn: this.importFn,
fetch: this.fetchFn,
logger: this.logger,
})),
securityCertConfig.password ||
(securityCertConfig.passwordPath &&
utils.readFileOrUrl(securityCertConfig.passwordPath, {
allowUnknownExtensions: true,
cwd: this.baseDir,
importFn: this.importFn,
fetch: this.fetchFn,
logger: this.logger,
})),
]);
soapClient.setSecurity(new soap.WSSecurityCert(privateKey, publicKey, password));
}
const schema = await soapGraphqlSchema({
soapClient,
logger: this.logger,
debug: !!crossHelpers.process.env.DEBUG,
warnings: !!crossHelpers.process.env.DEBUG,
schemaOptions: {
includePorts: this.config.includePorts,
includeServices: this.config.includeServices,
selectQueryOrMutationField: this.config.selectQueryOrMutationField,
selectQueryOperationsAuto: this.config.selectQueryOperationsAuto,
},
});
return {
schema,
};
}
}
module.exports = SoapHandler;