UNPKG

@graphql-mesh/soap

Version:
1,064 lines (1,050 loc) • 41.4 kB
'use strict'; 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;