From 1f3ff4d4dc70b7974db5389fa4acfc9653dab43c Mon Sep 17 00:00:00 2001 From: Sebastian Henke Date: Wed, 28 Jun 2023 10:38:20 +0200 Subject: [PATCH 1/6] Add dev server --- dev/index.ts | 25 +++++ dev/start-server.js | 2 + package-lock.json | 265 +++++++++++++++++++++++++++++++++++++------- package.json | 3 +- 4 files changed, 254 insertions(+), 41 deletions(-) create mode 100644 dev/index.ts create mode 100644 dev/start-server.js diff --git a/dev/index.ts b/dev/index.ts new file mode 100644 index 0000000..44eb140 --- /dev/null +++ b/dev/index.ts @@ -0,0 +1,25 @@ +import { soapGraphqlSchema } from '../src'; +import { createHandler } from 'graphql-http/lib/use/http'; +import * as http from 'http'; + +// http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL +// http://www.dataaccess.com/webservicesserver/numberconversion.wso?WSDL +// https://www.uid-wse.admin.ch/V5.0/PublicServices.svc?WSDL + +soapGraphqlSchema({ + debug: true, + createClient: { + url: 'http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL', + }, +}).then((schema) => { + const handler = createHandler({ schema }); + const server = http.createServer((req, res) => { + if (req.url.startsWith('/graphql')) { + handler(req, res); + } else { + res.writeHead(404).end(); + } + }); + server.listen(4000); + console.log(`serving graphql on http://localhost:4000/graphql`); +}); diff --git a/dev/start-server.js b/dev/start-server.js new file mode 100644 index 0000000..2aff6c2 --- /dev/null +++ b/dev/start-server.js @@ -0,0 +1,2 @@ +require('ts-node').register({ lazy: true }); +require('./index'); diff --git a/package-lock.json b/package-lock.json index f42de06..f0b7b40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "axios": "^0.27.2", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", - "graphql": "^16.6.0", + "graphql": "^16.7.1", + "graphql-http": "^1.19.0", "husky": "^8.0.1", "mocha": "^10.0.0", "prettier": "^2.7.1", @@ -114,9 +115,9 @@ "dev": true }, "node_modules/@xmldom/xmldom": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.6.tgz", - "integrity": "sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ==", + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.11.tgz", + "integrity": "sha512-UDi3g6Jss/W5FnSzO9jCtQwEpfymt0M+sPPlmLhDH6h2TJ8j4ESE/LpmNPBij15J5NKkk4/cg/qoVMdWI3vnlQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -321,6 +322,19 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -557,9 +571,9 @@ "dev": true }, "node_modules/dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "dependencies": { "asap": "^2.0.0", @@ -721,15 +735,15 @@ } }, "node_modules/formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", "dev": true, "dependencies": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" @@ -755,6 +769,12 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -773,6 +793,21 @@ "node": "*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -830,13 +865,25 @@ } }, "node_modules/graphql": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", - "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/graphql-http": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/graphql-http/-/graphql-http-1.19.0.tgz", + "integrity": "sha512-fOD3hfp/G+Lhx2FWW5HsfmtJSsw6CikcpOboG7/mFo/pPUzn3yOwKdTFRnJ8MVY4ru69MT1nSPr/1gI/iuGNlw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "graphql": ">=0.11 <=16" + } + }, "node_modules/graphql-scalars": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.18.0.tgz", @@ -851,6 +898,18 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -860,6 +919,30 @@ "node": ">=8" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1255,6 +1338,15 @@ "node": ">=8" } }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1492,10 +1584,13 @@ } }, "node_modules/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" }, @@ -1589,6 +1684,20 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -2019,9 +2128,9 @@ "dev": true }, "@xmldom/xmldom": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.6.tgz", - "integrity": "sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ==", + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.11.tgz", + "integrity": "sha512-UDi3g6Jss/W5FnSzO9jCtQwEpfymt0M+sPPlmLhDH6h2TJ8j4ESE/LpmNPBij15J5NKkk4/cg/qoVMdWI3vnlQ==", "dev": true }, "acorn": { @@ -2183,6 +2292,16 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -2359,9 +2478,9 @@ "dev": true }, "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "requires": { "asap": "^2.0.0", @@ -2472,15 +2591,15 @@ } }, "formidable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", - "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", "dev": true, "requires": { - "dezalgo": "1.0.3", - "hexoid": "1.0.0", - "once": "1.4.0", - "qs": "6.9.3" + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" } }, "fs.realpath": { @@ -2496,6 +2615,12 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2508,6 +2633,18 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -2549,9 +2686,16 @@ } }, "graphql": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", - "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==" + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==" + }, + "graphql-http": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/graphql-http/-/graphql-http-1.19.0.tgz", + "integrity": "sha512-fOD3hfp/G+Lhx2FWW5HsfmtJSsw6CikcpOboG7/mFo/pPUzn3yOwKdTFRnJ8MVY4ru69MT1nSPr/1gI/iuGNlw==", + "dev": true, + "requires": {} }, "graphql-scalars": { "version": "1.18.0", @@ -2561,12 +2705,33 @@ "tslib": "~2.4.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2856,6 +3021,12 @@ "path-key": "^3.0.0" } }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3017,10 +3188,13 @@ } }, "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } }, "randombytes": { "version": "2.1.0", @@ -3082,6 +3256,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", diff --git a/package.json b/package.json index 58be784..c350cf2 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "axios": "^0.27.2", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", - "graphql": "^16.6.0", + "graphql": "^16.7.1", + "graphql-http": "^1.19.0", "husky": "^8.0.1", "mocha": "^10.0.0", "prettier": "^2.7.1", From 4d3499ff85ed59cc5d150f72b4adb7c0260e3c3b Mon Sep 17 00:00:00 2001 From: Sebastian Henke Date: Wed, 28 Jun 2023 11:08:40 +0200 Subject: [PATCH 2/6] feature: rework type generation [breaking] - make apis provided by the generated schema as stable as possible (introduce namespaces for types, don't return only the first operation output message part, fallback to GraphQLJSON on generation errors and incompatible xsd to preserve a working (but partly untyped) schema). - Set a more versatile foundation for additional xsd features which can be implemented later without breaking changes. - Fix errors around the type resolution in different namespaces. - Remove the usage of node-soap's service descriptions because they are incomplete. Unfortunately node-soap seems to rely on them too which leads to some bugs. - Remove specs which don't work anymore because of removed WSDL sources Fixes #16 and partially #17 and #18 --- spec/node-soap-graphql.spec.ts | 241 +--------- src/node-soap/node-soap-caller.ts | 8 +- src/node-soap/node-soap-endpoint.ts | 73 +-- src/node-soap/node-soap-resolver.ts | 549 ++++++++++------------- src/soap2graphql/custom-type-resolver.ts | 29 +- src/soap2graphql/name-resolver.ts | 38 +- src/soap2graphql/schema-resolver.ts | 218 ++++++--- src/soap2graphql/soap-endpoint.ts | 23 +- src/soap2graphql/soap2graphql.ts | 12 +- 9 files changed, 527 insertions(+), 664 deletions(-) diff --git a/spec/node-soap-graphql.spec.ts b/spec/node-soap-graphql.spec.ts index 15a45f3..2683c2b 100644 --- a/spec/node-soap-graphql.spec.ts +++ b/spec/node-soap-graphql.spec.ts @@ -1,108 +1,14 @@ import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; import { expect } from 'chai'; -import { after, afterEach, before, beforeEach, describe, fail, it, xit } from 'mocha'; +import * as chaiAsPromised from 'chai-as-promised'; +import { describe, it, xit } from 'mocha'; import { GraphQLSchema } from 'graphql/type/schema'; -import { getIntrospectionQuery } from 'graphql'; -import { - graphql, - printSchema, - GraphQLInputType, - GraphQLEnumType, - GraphQLEnumValueConfigMap, -} from 'graphql'; -import { inspect } from 'util'; -import { GraphQLBoolean } from 'graphql/type/scalars'; -import { - soapGraphqlSchema, - SoapCallInput, - CustomTypeResolver, - DefaultTypeResolver, - SoapGraphqlOptions, - NodeSoapClient, - SoapCaller, - NodeSoapCaller, - createSoapClient, - createLogger, -} from '../index'; +import { getIntrospectionQuery, graphql } from 'graphql'; +import { SoapGraphqlOptions, soapGraphqlSchema } from '../index'; chai.use(chaiAsPromised); describe('call soap endpoints', () => { - xit('http://www.webservicex.net/geoipservice.asmx?WSDL', async () => { - await queryEndpoint( - { createClient: { url: 'http://www.webservicex.net/geoipservice.asmx?WSDL' } }, - ` - mutation { - GetGeoIP(IPAddress: "74.125.224.72") { - IP - ReturnCode - } - } - `, - (data) => { - expect(data.GetGeoIP.IP).to.exist; - expect(data.GetGeoIP.IP).to.equal('74.125.224.72'); - expect(data.GetGeoIP.ReturnCode).to.exist; - expect(data.GetGeoIP.ReturnCode).to.equal(1); - }, - ); - }).timeout(5000); - - it('http://ws.cdyne.com/ip2geo/ip2geo.asmx?wsdl', async () => { - await queryEndpoint( - { createClient: { url: 'http://ws.cdyne.com/ip2geo/ip2geo.asmx?wsdl' } }, - ` - mutation { - ResolveIP(ipAddress: "74.125.224.72", licenseKey: "") { - Latitude - Longitude - AreaCode - } - } - `, - (data) => { - expect(data.ResolveIP).to.exist; - expect(data.ResolveIP.Latitude).to.exist; - expect(data.ResolveIP.Longitude).to.exist; - expect(data.ResolveIP.AreaCode).to.exist; - }, - ); - }).timeout(5000); - - xit('http://www.webservicex.net/globalweather.asmx?WSDL', async () => { - await queryEndpoint( - { createClient: { url: 'http://www.webservicex.net/globalweather.asmx?WSDL' } }, - ` - mutation { - GetCitiesByCountry(CountryName: "Germany") - } - `, - (data) => { - expect(data.GetCitiesByCountry).to.exist; - expect(data.GetCitiesByCountry).to.include('Wunstorf'); - }, - ); - }).timeout(5000); - - xit('http://www.webservicex.net/periodictable.asmx?WSDL', async () => { - await queryEndpoint( - { createClient: { url: 'http://www.webservicex.net/periodictable.asmx?WSDL' } }, - ` - mutation { - atoms: GetAtoms - atoms2: GetAtoms - } - `, - (data) => { - expect(data.atoms).to.exist; - expect(data.atoms2).to.exist; - expect(data.atoms).to.include('Aluminium'); - expect(data.atoms2).to.include('Aluminium'); - }, - ); - }).timeout(5000); - it('http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL', async () => { await queryEndpoint( { @@ -111,7 +17,7 @@ describe('call soap endpoints', () => { }, }, ` - fragment CountryInfo on TCountryInfo { + fragment CountryInfo on OorsprongOrgWebsamplesCountryinfo_TCountryInfo { sName sCurrencyISOCode Languages { @@ -124,17 +30,19 @@ describe('call soap endpoints', () => { mutation { FullCountryInfo(sCountryISOCode: "PL") { - ...CountryInfo + FullCountryInfoResult { + ...CountryInfo + } } } `, (data) => { - expect(data.FullCountryInfo).to.exist; - expect(data.FullCountryInfo.sName).to.equal('Poland'); - expect(data.FullCountryInfo.sCurrencyISOCode).to.equal('PLN'); - expect(data.FullCountryInfo.Languages).to.exist; - expect(data.FullCountryInfo.Languages.tLanguage).to.exist; - expect(data.FullCountryInfo.Languages.tLanguage[0]).to.exist; + expect(data.FullCountryInfo.FullCountryInfoResult).to.exist; + expect(data.FullCountryInfo.FullCountryInfoResult.sName).to.equal('Poland'); + expect(data.FullCountryInfo.FullCountryInfoResult.sCurrencyISOCode).to.equal('PLN'); + expect(data.FullCountryInfo.FullCountryInfoResult.Languages).to.exist; + expect(data.FullCountryInfo.FullCountryInfoResult.Languages.tLanguage).to.exist; + expect(data.FullCountryInfo.FullCountryInfoResult.Languages.tLanguage[0]).to.exist; }, ); }).timeout(5000); @@ -148,125 +56,16 @@ describe('call soap endpoints', () => { }, ` mutation { - NumberToWords(ubiNum: 1234) - } - `, - (data) => { - expect(data.NumberToWords).to.exist; - expect(data.NumberToWords).to.equal('one thousand two hundred and thirty four'); - }, - ); - }).timeout(5000); - - xit('http://www.webservicex.net/Astronomical.asmx?WSDL', async () => { - class AstronomicalResolver extends DefaultTypeResolver { - inputType(typeName: string): GraphQLInputType { - if ( - typeName === 'string|meters,kilometers,miles,AstronmicalunitAU,lightyear,parsec' - ) { - return new GraphQLEnumType({ - name: 'AstronomicalUnit', - values: { - meters: {}, - kilometers: {}, - miles: {}, - AstronmicalunitAU: {}, - lightyear: {}, - parsec: {}, - }, - }); - } - return super.inputType(typeName); - } - } - - await queryEndpoint( - { - createClient: { url: 'http://www.webservicex.net/Astronomical.asmx?WSDL' }, - schemaOptions: { customResolver: new AstronomicalResolver() }, - }, - ` - mutation { - ChangeAstronomicalUnit(AstronomicalValue: 1.24, fromAstronomicalUnit: lightyear, toAstronomicalUnit: parsec) - } - `, - (data) => { - expect(data.ChangeAstronomicalUnit).to.exist; - expect(data.ChangeAstronomicalUnit).to.equal(0.3801865856585023); - }, - ); - }).timeout(5000); - - xit('http://www.webservicex.net/CurrencyConvertor.asmx?WSDL', async () => { - class CurrencyResolver extends DefaultTypeResolver { - inputType(typeName: string): GraphQLInputType { - if ( - !!typeName && - typeName.startsWith('string|AFA,ALL,DZD,ARS,AWG,AUD,BSD,BHD,BDT,BBD') - ) { - const values: GraphQLEnumValueConfigMap = {}; - typeName - .substring(7) - .split(',') - .forEach((currencyCode: string) => { - values[currencyCode] = {}; - }); - - return new GraphQLEnumType({ - name: 'Currency', - values: values, - }); + NumberToWords(ubiNum: 1234) { + NumberToWordsResult } - return super.inputType(typeName); - } - } - - await queryEndpoint( - { - createClient: { url: 'http://www.webservicex.net/CurrencyConvertor.asmx?WSDL' }, - schemaOptions: { customResolver: new CurrencyResolver() }, - }, - ` - mutation { - ConversionRate(FromCurrency: AFA, ToCurrency: ALL) - } - `, - ); - }).timeout(5000); - - xit('http://soatest.parasoft.com/calculator.wsdl', async () => { - class CalculatorCaller extends NodeSoapCaller { - async createGraphqlResult(input: SoapCallInput, result: any): Promise { - return result['Result']['$value']; - } - } - - const soapClient: NodeSoapClient = await createSoapClient( - 'http://soatest.parasoft.com/calculator.wsdl', - ); - const caller: SoapCaller = new CalculatorCaller(soapClient, createLogger(false, false)); - const options: SoapGraphqlOptions = { - soapClient: soapClient, - soapCaller: caller, - debug: false, - }; - - await queryEndpoint( - options, - ` - mutation { - sum1: add(x: 1, y: 2) - sum2: add(x: 2.3, y: 3.45) - divide(numerator: 2, denominator: 4) } `, (data) => { - expect(data.sum1).to.exist; - expect(data.sum1).to.equal(3); - expect(data.sum2).to.exist; - expect(data.sum2).to.equal(5.75); - expect(data.divide).to.exist; - expect(data.divide).to.equal(0.5); + expect(data.NumberToWords).to.exist; + expect(data.NumberToWords.NumberToWordsResult).to.equal( + 'one thousand two hundred and thirty four', + ); }, ); }).timeout(5000); diff --git a/src/node-soap/node-soap-caller.ts b/src/node-soap/node-soap-caller.ts index 6cf5f80..6e66aaf 100644 --- a/src/node-soap/node-soap-caller.ts +++ b/src/node-soap/node-soap-caller.ts @@ -84,13 +84,7 @@ export class NodeSoapCaller implements SoapCaller { this.debug( () => `operation '${input.operation.name()}' returned '${inspect(result, false, 5)}'`, ); - - if (!input.operation.resultField()) { - // void operation - return !result ? null : JSON.stringify(result); - } else { - return !result ? null : result[input.operation.resultField()]; - } + return result; } protected debug(message: LateResolvedMessage): void { diff --git a/src/node-soap/node-soap-endpoint.ts b/src/node-soap/node-soap-endpoint.ts index 9d2f7d8..0101bc1 100644 --- a/src/node-soap/node-soap-endpoint.ts +++ b/src/node-soap/node-soap-endpoint.ts @@ -1,17 +1,14 @@ import { SoapEndpoint, - SoapType, - SoapObjectType, - SoapField, - SoapService, - SoapPort, SoapOperation, - SoapOperationArg, + SoapPort, + SoapService, + SoapType, } from '../soap2graphql/soap-endpoint'; -import { NodeSoapClient, NodeSoapOptions } from './node-soap'; -import { inspect } from 'util'; +import { NodeSoapClient } from './node-soap'; import { NodeSoapWsdlResolver } from './node-soap-resolver'; import { Logger } from '../soap2graphql/logger'; +import { IPort, OperationElement, ServiceElement } from 'soap/lib/wsdl/elements'; export function createSoapEndpoint(soapClient: NodeSoapClient, logger: Logger): SoapEndpoint { return new NodeSoapEndpoint(soapClient, logger); @@ -30,9 +27,9 @@ export class NodeSoapEndpoint implements SoapEndpoint { services(): NodeSoapService[] { const services: NodeSoapService[] = []; - const content: any = this.describe(); - for (let key in content) { - services.push(new NodeSoapService(this, key, content[key])); + const serviceElements = this.soapClient.wsdl.definitions.services; + for (let key in serviceElements) { + services.push(new NodeSoapService(this, key, serviceElements[key])); } return services; } @@ -51,7 +48,11 @@ export class NodeSoapEndpoint implements SoapEndpoint { } export class NodeSoapService implements SoapService { - constructor(private _wsdl: NodeSoapEndpoint, private _name: string, private _content: any) {} + constructor( + private _wsdl: NodeSoapEndpoint, + private _name: string, + private _serviceElement: ServiceElement, + ) {} endpoint(): NodeSoapEndpoint { return this._wsdl; @@ -71,15 +72,19 @@ export class NodeSoapService implements SoapService { private createPorts(): NodeSoapPort[] { const ports: NodeSoapPort[] = []; - for (let key in this._content) { - ports.push(new NodeSoapPort(this, key, this._content[key])); + for (let key in this._serviceElement.ports) { + ports.push(new NodeSoapPort(this, key, this._serviceElement.ports[key])); } return ports; } } export class NodeSoapPort implements SoapPort { - constructor(private _service: NodeSoapService, private _name: string, private _content: any) {} + constructor( + private _service: NodeSoapService, + private _name: string, + private _portElement: IPort, + ) {} endpoint(): NodeSoapEndpoint { return this.service().endpoint(); @@ -103,15 +108,21 @@ export class NodeSoapPort implements SoapPort { private createOperations(): NodeSoapOperation[] { const operations: NodeSoapOperation[] = []; - for (let key in this._content) { - operations.push(new NodeSoapOperation(this, key, this._content[key])); + for (let key in this._portElement.binding.methods) { + operations.push( + new NodeSoapOperation(this, key, this._portElement.binding.methods[key]), + ); } return operations; } } export class NodeSoapOperation implements SoapOperation { - constructor(private _port: NodeSoapPort, private _name: string, private _content: any) {} + constructor( + private _port: NodeSoapPort, + private _name: string, + private _operationElement: OperationElement, + ) {} endpoint(): NodeSoapEndpoint { return this.port().endpoint(); @@ -129,33 +140,27 @@ export class NodeSoapOperation implements SoapOperation { return this._name; } - content(): any { - return this._content; + operationElement(): OperationElement { + return this._operationElement; } - private _inputs: SoapField[] = null; - args(): SoapField[] { - if (!this._inputs) { - this._inputs = this.endpoint().resolver().createOperationArgs(this); + private _input: SoapType = null; + inputType(): SoapType { + if (!this._input) { + this._input = this.endpoint().resolver().createOperationArgs(this); } - return this._inputs; + return this._input; } - private _output: { type: { type: SoapType; isList: boolean }; resultField: string } = null; + private _output: { type: SoapType; isList: boolean } = null; output(): { type: SoapType; isList: boolean } { if (!this._output) { this._output = this.createOutput(); } - return this._output.type; - } - resultField(): string { - if (!this._output) { - this._output = this.createOutput(); - } - return this._output.resultField; + return this._output; } - private createOutput(): { type: { type: SoapType; isList: boolean }; resultField: string } { + private createOutput(): { type: SoapType; isList: boolean } { return this.endpoint().resolver().createOperationOutput(this); } } diff --git a/src/node-soap/node-soap-resolver.ts b/src/node-soap/node-soap-resolver.ts index cc27fc1..41c3735 100644 --- a/src/node-soap/node-soap-resolver.ts +++ b/src/node-soap/node-soap-resolver.ts @@ -1,45 +1,32 @@ import { - SoapType, - SoapOperationArg, - SoapObjectType, + SoapComplexType, SoapField, + SoapSimpleType, + SoapType, } from '../soap2graphql/soap-endpoint'; import { inspect } from 'util'; -import { NodeSoapOperation, NodeSoapEndpoint } from './node-soap-endpoint'; -import { NodeSoapClient, NodeSoapWsdl } from './node-soap'; -import { Logger, LateResolvedMessage } from '../soap2graphql/logger'; - -// an content object ... basically a plain JS object -type WsdlContent = { [key: string]: any }; -// a type definition in the WSDL; -// Content: a complete definition of the type (fields etc.) -// string: a primitive type (e.g. "xs:string") -// null: ...probably a faulty WSDL -type WsdlTypeContent = WsdlContent | string | null; - -// input content of an operation, defines the args of the operation -type WsdlInputContent = { [argName: string]: WsdlArgContent } | {} | null; -type WsdlArgContent = WsdlTypeContent; - -// output content of an operation, defines the result type -type WsdlOutputContent = { resultFieldName: WsdlResultContent } | {} | null; -type WsdlResultContent = WsdlTypeContent; - -type XsdTypeDefinition = { name: 'complexType'; $name: string; children: XsdTypeDefinitionBody[] }; - -type XsdTypeDefinitionBody = XsdSequence | XsdComplexType; - -type XsdSequence = { name: 'sequence'; children: XsdFieldDefinition[] }; - -type XsdComplexType = { name: 'complexContent'; children: XsdExtension[] }; -type XsdExtension = { name: 'extension'; $base: string; children: XsdSequence[] }; - -type XsdFieldDefinition = { - $name: string; - $targetNamespace: string; - $type: string | undefined; - $maxOccurs?: 'unbounded' | string; - children: XsdTypeDefinition[] | undefined; +import { NodeSoapOperation } from './node-soap-endpoint'; +import { NodeSoapWsdl } from './node-soap'; +import { LateResolvedMessage, Logger } from '../soap2graphql/logger'; +import { + ComplexContentElement, + ComplexTypeElement, + Element, + ElementElement, + ExtensionElement, + InputElement, + RestrictionElement, + SequenceElement, + SimpleTypeElement, +} from 'soap/lib/wsdl/elements'; +import { isListType } from 'graphql/type'; + +type XsdSupportedTypeDefinition = ComplexTypeElement | SimpleTypeElement | ElementElement; + +const XS_STRING: SoapType = { + kind: 'simpleType', + name: 'string', + namespace: 'http://www.w3.org/2001/XMLSchema', }; export class NodeSoapWsdlResolver { @@ -55,8 +42,8 @@ export class NodeSoapWsdlResolver { this.logger.debug(message); } - createOperationArgs(operation: NodeSoapOperation): SoapOperationArg[] { - const inputContent: WsdlInputContent = operation.content()['input']; + createOperationArgs(operation: NodeSoapOperation): SoapType { + const inputContent: InputElement = operation.operationElement()['input']; this.debug( () => @@ -71,56 +58,22 @@ export class NodeSoapWsdlResolver { this.warn(() => `no input definition for operation '${operation.name()}'`); } - // inputContent===null -> argNames===[] - const argNames: string[] = nonNamespaceKeys(inputContent); - const inputNamespace = inputContent && targetNamespace(inputContent); - - const args: SoapOperationArg[] = argNames - .map((key: string) => { - return this.createOperationArg(operation, inputNamespace, key, inputContent[key]); - }) - .filter((arg) => !!arg); - - return args; - } - - private createOperationArg( - operation: NodeSoapOperation, - inputNamespace: string, - argWsdlFieldName: string, - argContent: WsdlArgContent, - ): SoapOperationArg { - this.debug( - () => - `creating arg for operation '${operation.name()}' from content '${inspect( - argContent, - false, - 5, - )}'`, - ); - - const parsedArgName: { name: string; isList: boolean } = - parseWsdlFieldName(argWsdlFieldName); + if (!inputContent.$lookupType) { + // no args + return undefined; + } - const inputType: SoapType = this.resolveContentToSoapType( - inputNamespace, - argContent, - `arg '${argWsdlFieldName}' of operation '${operation.name()}'`, + const ns = resolveNamespace(inputContent); + const inputType = this.resolveWsdlNameToSoapType( + ns, + withoutNamespace(inputContent.$lookupType), + `args of operation '${operation.name()}'`, ); - - const input: SoapOperationArg = { - name: parsedArgName.name, - type: inputType, - isList: parsedArgName.isList, - }; - return input; + return inputType; } - createOperationOutput(operation: NodeSoapOperation): { - type: { type: SoapType; isList: boolean }; - resultField: string; - } { - const outputContent: WsdlOutputContent = operation.content()['output']; + createOperationOutput(operation: NodeSoapOperation): { type: SoapType; isList: boolean } { + const outputContent: ElementElement = operation.operationElement()['output']; this.debug( () => @@ -130,125 +83,18 @@ export class NodeSoapWsdlResolver { 5, )}'`, ); - - // determine type and field name - let resultType: SoapType; - let resultFieldName: string; - const outputNamespace = targetNamespace(outputContent); - const ownerStringForLog: string = `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: string[] = 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: WsdlResultContent = 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); - + const ns = resolveNamespace(outputContent); + const outputType = this.resolveWsdlNameToSoapType( + ns, + withoutNamespace(outputContent.$lookupType), + `return type of operation '${operation.name()}`, + ); return { - type: { - type: resultType, - isList: parsedResultFieldName.isList, - }, - resultField: parsedResultFieldName.name, + type: outputType, + isList: isListType(outputContent), }; } - private resolveContentToSoapType( - parentNamespace: string, - typeContent: WsdlTypeContent, - ownerStringForLog: string, - ): SoapType { - this.debug( - () => - `resolving soap type for ${ownerStringForLog} from content '${inspect( - typeContent, - false, - 3, - )}'`, - ); - - // 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); - } - - private findTypeName(content: WsdlTypeContent): string { - const types = this.wsdl.definitions.descriptions.types; - for (let key in types) { - if (types[key] === content) { - return key; - } - } - return null; - } - resolveWsdlNameToSoapType( namespace: string, wsdlTypeName: string, @@ -272,77 +118,124 @@ export class NodeSoapWsdlResolver { } // get the defition of the type from the schema section in the WSDL - const xsdTypeDefinition: XsdTypeDefinition = this.findXsdTypeDefinition( - namespace, - wsdlTypeName, - ); + const schemaObject = this.wsdl.findSchemaObject(namespace, wsdlTypeName); - if (!xsdTypeDefinition) { + if (!schemaObject) { // has no type definition - // --> primitive type, e.g. 'string' - const soapType: string = wsdlTypeName; + // This happens in case of bad wsdl/xsd or in case of XMLSchema defined simple types + const soapType: SoapSimpleType = { + kind: 'simpleType', + name: wsdlTypeName, + namespace: namespace, + }; this.alreadyResolved.set(namespace + wsdlTypeName, soapType); this.debug( () => - `resolved namespace: '${namespace}', typeName: '${wsdlTypeName}' to primitive type '${soapType}'`, + `resolved namespace: '${namespace}', typeName: '${wsdlTypeName}' to simple type '${soapType}'`, ); - return soapType; - } else { - // create a new object type - const soapType: SoapObjectType = { - 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); + let soapType: SoapType; + switch (schemaObject.constructor) { + case ComplexTypeElement: + soapType = { + kind: 'complexType', + name: schemaObject.$name, + namespace: namespace, + base: null, + fields: null, + }; + break; + case SimpleTypeElement: + soapType = { + kind: 'simpleType', + name: schemaObject.$name, + namespace: namespace, + base: null, + }; + break; + case ElementElement: + if (schemaObject.$type) { + const ns = resolveNamespace(schemaObject); + const type = this.findXsdTypeDefinition(ns, schemaObject.$type); + return this.resolveWsdlNameToSoapType( + ns, + withoutNamespace(schemaObject.$lookupType), + `return type of element '${schemaObject.$name}`, + ); + } else { + // must be anonymous + return this.resolveAnonymousTypeToSoapType(schemaObject, schemaObject.$name); + } + default: + soapType = XS_STRING; + this.warn( + () => + `unsupported element '${schemaObject.name}' as operation message part. Using string.`, + ); + } - this.debug( - () => - `resolved namespace: '${namespace}', typeName: '${wsdlTypeName}' to object type '${inspect( - soapType, - false, - 3, - )}'`, - ); + this.alreadyResolved.set(namespace + wsdlTypeName, soapType); - return soapType; + // resolve bindings (field types, base type) after type has been registered to resolve circular dependencies + if (!(soapType instanceof ElementElement)) { + try { + this.resolveTypeBody(soapType, namespace, schemaObject); + } catch (e) { + if (soapType.kind === 'complexType') { + soapType.fields = undefined; + } else { + soapType = XS_STRING; + } + } } + + this.debug( + () => + `resolved namespace: '${namespace}', typeName: '${wsdlTypeName}' to object type '${inspect( + soapType, + false, + 3, + )}'`, + ); + return soapType; } private resolveAnonymousTypeToSoapType( - xsdFieldDefinition: XsdFieldDefinition, - parentSoapType: SoapObjectType, + xsdFieldDefinition: ElementElement, + generatedTypeName: string, ): SoapType { - const namespace = xsdFieldDefinition.$targetNamespace; - const ownerStringForLog = `field '${xsdFieldDefinition.$name}' of soap type '${parentSoapType.name}'`; + const namespace = resolveNamespace(xsdFieldDefinition); + let existingDef = this.findXsdTypeDefinition(namespace, generatedTypeName); + while (!!existingDef && !(existingDef instanceof ElementElement)) { + generatedTypeName = generatedTypeName + '_'; + existingDef = this.findXsdTypeDefinition(namespace, generatedTypeName); + } + const ownerStringForLog = `field '${xsdFieldDefinition.$name}' of soap type '${generatedTypeName}'`; this.debug( () => `resolving anonymous type for ${ownerStringForLog} from namespace '${namespace}'`, ); - - let generatedTypeName = `${parentSoapType.name}_${capitalizeFirstLetter( - xsdFieldDefinition.$name, - )}`; - while (!!this.findXsdTypeDefinition(namespace, generatedTypeName)) { - generatedTypeName = generatedTypeName + '_'; - } - - const soapType: SoapObjectType = { + // TODO: fix for simple types + const soapType: SoapComplexType = { + kind: 'complexType', name: generatedTypeName, + namespace: xsdFieldDefinition.$targetNamespace, base: null, fields: null, }; // resolve bindings (field types, base type) - const bodyTypeDefinition: XsdTypeDefinition = xsdFieldDefinition.children + const bodyTypeDefinition = xsdFieldDefinition.children ? xsdFieldDefinition.children[0] : undefined; if (bodyTypeDefinition) { - this.resolveTypeBody(soapType, namespace, bodyTypeDefinition); + this.resolveTypeBody( + soapType, + namespace, + bodyTypeDefinition as XsdSupportedTypeDefinition, + ); this.debug( () => `resolved namespace: '${namespace}', typeName: '${generatedTypeName}' to object type '${inspect( @@ -360,14 +253,14 @@ export class NodeSoapWsdlResolver { return soapType; } - private findXsdTypeDefinition(namespace: string, typeName: string): XsdTypeDefinition { + private findXsdTypeDefinition(namespace: string, typeName: string): XsdSupportedTypeDefinition { return this.wsdl.findSchemaObject(namespace, typeName); } private resolveTypeBody( - soapType: SoapObjectType, + soapType: SoapType, namespace: string, - typeDefinition: XsdTypeDefinition, + typeDefinition: ComplexTypeElement | SimpleTypeElement, ): void { this.debug( () => @@ -382,48 +275,80 @@ export class NodeSoapWsdlResolver { const typeName: string = typeDefinition.$name; - let fields: XsdFieldDefinition[] = null; + let fields: Element[]; let baseTypeName: string = null; - const body: XsdTypeDefinitionBody = typeDefinition.children[0]; - - if (body.name === 'sequence') { - const sequence: XsdSequence = body; - fields = sequence.children || []; - } else if (body.name === 'complexContent') { - const extension: XsdExtension = body.children[0]; - const sequence: XsdSequence = 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 body: Element = typeDefinition.children[0]; + switch (body?.constructor) { + case SequenceElement: + fields = this.extractChildren(body as SequenceElement); + break; + case ComplexContentElement: + const extensionOrRestriction = body.children.find( + (child) => + child instanceof ExtensionElement || child instanceof RestrictionElement, + ) as ExtensionElement | RestrictionElement; + baseTypeName = extensionOrRestriction.$base; + const sequence: SequenceElement = extensionOrRestriction.children.find( + (child) => child instanceof SequenceElement, + ) as SequenceElement; + if (!sequence) { + this.warn( + () => + `cannot parse fields for soap type '${typeName}' as currently only restrictions/extensions with sequence are supported. leaving fields empty`, + ); + throw new Error('Unsupported xsd definition.'); + } else { + fields = this.extractChildren(sequence); + } + baseTypeName = withoutNamespace(extensionOrRestriction.$base); + break; + case SimpleTypeElement: + const restriction = body.children.find( + (child) => child instanceof RestrictionElement, + ) as RestrictionElement; + baseTypeName = withoutNamespace(restriction.$base); + break; + default: + this.warn(() => `cannot parse fields for soap type '${typeName}'`); + fields = undefined; } - const soapFields: SoapField[] = fields.map((field: XsdFieldDefinition) => { - let 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, - isList: !!field.$maxOccurs && field.$maxOccurs === 'unbounded', - }; - }); + // todo extract + if (soapType.kind === 'complexType' && fields) { + const soapFields: SoapField[] = fields.map((field: ElementElement) => { + let type; + if (field.$type) { + const ns = resolveNamespace(field); + type = this.resolveWsdlNameToSoapType( + ns, + withoutNamespace(field.$type), + `field '${field.$name}' of soap type '${soapType.name}'`, + ); + } else if (field.children.length) { + let generatedTypeName = `${soapType.name}_${capitalizeFirstLetter( + field.$name, + )}`; + type = this.resolveAnonymousTypeToSoapType(field, generatedTypeName); + } else { + this.warn( + () => + `Impossible to generate type for field without type and without children, using string for '${field.$name}'.`, + ); + type = XS_STRING; + } + return { + name: field.$name, + type, + isList: getIsList(field), + }; + }); + soapType.fields = soapFields; + } - // @todo in XSD it is possible to inherit a type from a primitive ... may have to handle this - const baseType: SoapObjectType = !baseTypeName + const baseType: SoapComplexType = !baseTypeName ? null - : ( + : ( this.resolveWsdlNameToSoapType( namespace, baseTypeName, @@ -431,49 +356,59 @@ export class NodeSoapWsdlResolver { ) ); - soapType.fields = soapFields; soapType.base = baseType; } -} -function nonNamespaceKeys(obj: any): string[] { - return !obj ? [] : Object.keys(obj).filter((key) => !isNamespaceKey(key)); + private extractChildren(sequence: SequenceElement): Element[] { + const result: Element[] = []; + sequence.children.forEach((child) => { + switch (child.constructor) { + case ElementElement: + result.push(child); + break; + default: + this.warn( + () => `${child.name} is currently not supported in field '${child.$name}'.`, + ); + throw new Error('Unsupported definition'); + } + }); + return result; + } } function targetNamespace(content: any) { return content['targetNamespace']; } -function isNamespaceKey(key: string): boolean { - return key === 'targetNSAlias' || key === 'targetNamespace'; +export function withoutNamespace(value: string): string { + if (!value) { + return value; + } + const matcher: RegExpMatchArray = value.match(/[a-zA-Z0-9]+:(.+)/); + return !matcher || matcher.length < 2 ? value : matcher[1]; } -function withoutNamespace(value: string): string { +function namespacePrefix(value: string): string { if (!value) { return value; } - const matcher: RegExpMatchArray = value.match(/[a-zA-Z0-9]+\:(.+)/); + const matcher: RegExpMatchArray = value.match(/([a-zA-Z0-9]+):.+/); return !matcher || matcher.length < 2 ? value : matcher[1]; } -function isWsdlListFieldName(wsdlFieldName: string): boolean { - return !!wsdlFieldName && wsdlFieldName.endsWith('[]'); +function capitalizeFirstLetter(value: string) { + return value.charAt(0).toUpperCase() + value.substring(1); } -function parseWsdlFieldName(wsdlFieldName: string): { name: string; isList: boolean } { - if (isWsdlListFieldName(wsdlFieldName)) { - return { - name: wsdlFieldName.substring(0, wsdlFieldName.length - 2), - isList: true, - }; - } else { - return { - name: wsdlFieldName, - isList: false, - }; - } +function getIsList(field: { $maxOccurs?: string }): boolean { + return !!field.$maxOccurs && field.$maxOccurs === 'unbounded'; } -function capitalizeFirstLetter(value: string) { - return value.charAt(0).toUpperCase() + value.substring(1); +function resolveNamespace(element: ElementElement) { + const prefix = namespacePrefix(element.$type || element.$lookupType); + // if (element.ignoredNamespaces?.includes(prefix)) { + // return element.$targetNamespace + // } + return element.xmlns[prefix] || element.schemaXmlns[prefix] || element.$targetNamespace; } diff --git a/src/soap2graphql/custom-type-resolver.ts b/src/soap2graphql/custom-type-resolver.ts index c82c970..92ab641 100644 --- a/src/soap2graphql/custom-type-resolver.ts +++ b/src/soap2graphql/custom-type-resolver.ts @@ -1,14 +1,14 @@ import { - GraphQLScalarType, - GraphQLID, - GraphQLString, - GraphQLFloat, GraphQLBoolean, + GraphQLFloat, + GraphQLID, + GraphQLInputType, GraphQLInt, GraphQLOutputType, - GraphQLInputType, + GraphQLScalarType, + GraphQLString, } from 'graphql'; -import { GraphQLDateTime, GraphQLTime, GraphQLDate } from 'graphql-scalars'; +import { GraphQLDate, GraphQLDateTime, GraphQLTime } from 'graphql-scalars'; /** * Resolver that converts WSDL types, that are not fully defined in the WSDL itself, to GraphQL types. @@ -17,9 +17,10 @@ import { GraphQLDateTime, GraphQLTime, GraphQLDate } from 'graphql-scalars'; * You can provide your own resolver to handle custom types of your WSDL. * But you must still provide resolvment for primitive types like 'string', e.g by re-using DefaultTypeResolver. */ + export interface CustomTypeResolver { - outputType(typeName: string): GraphQLOutputType; - inputType(typeName: string): GraphQLInputType; + outputType(typeName: string, namespace: string): GraphQLOutputType; + inputType(typeName: string, namespace: string): GraphQLInputType; } /** @@ -76,15 +77,15 @@ export class DefaultTypeResolver implements CustomTypeResolver { date = GraphQLDate; time = GraphQLTime; - resolve(typeName: string): GraphQLScalarType { - return this[typeName]; + resolve(typeName: string, namespace: string): GraphQLScalarType { + return this[`${namespace}:${typeName}`] || this[typeName]; } - outputType(typeName: string): GraphQLOutputType { - return this.resolve(typeName); + outputType(typeName: string, namespace: string): GraphQLOutputType { + return this.resolve(typeName, namespace); } - inputType(typeName: string): GraphQLInputType { - return this.resolve(typeName); + inputType(typeName: string, namespace: string): GraphQLInputType { + return this.resolve(typeName, namespace); } } diff --git a/src/soap2graphql/name-resolver.ts b/src/soap2graphql/name-resolver.ts index 68d3f28..9ba74c4 100644 --- a/src/soap2graphql/name-resolver.ts +++ b/src/soap2graphql/name-resolver.ts @@ -1,23 +1,47 @@ -import { SoapObjectType } from './soap-endpoint'; +import { SoapComplexType, SoapType } from './soap-endpoint'; -export type NameResolver = (soapType: SoapObjectType) => string; +export type NameResolver = (soapType: SoapType) => string; -export const defaultOutputNameResolver: NameResolver = (soapType: SoapObjectType) => { - return !soapType ? null : !soapType.name ? null : capitalizeFirstLetter(soapType.name); +export const defaultOutputNameResolver: NameResolver = (soapType: SoapType) => { + return !soapType + ? null + : !soapType.name + ? null + : capitalizeFirstLetter(namespacedGraphQLTypeName(soapType)); +}; + +export const defaultInputNameResolver: NameResolver = (soapType: SoapType) => { + return !soapType + ? null + : !soapType.name + ? null + : capitalizeFirstLetter(namespacedGraphQLTypeName(soapType)) + 'Input'; }; -export const defaultInputNameResolver: NameResolver = (soapType: SoapObjectType) => { +export const defaultScalarNameResolver: NameResolver = (soapType: SoapType) => { return !soapType ? null : !soapType.name ? null - : capitalizeFirstLetter(soapType.name) + 'Input'; + : capitalizeFirstLetter(namespacedGraphQLTypeName(soapType)) + 'Input'; }; -export const defaultInterfaceNameResolver: NameResolver = (soapType: SoapObjectType) => { +export const defaultInterfaceNameResolver: NameResolver = (soapType: SoapComplexType) => { return !soapType ? null : !soapType.name ? null : 'i' + capitalizeFirstLetter(soapType.name); }; function capitalizeFirstLetter(value: string) { return value.charAt(0).toUpperCase() + value.substring(1); } + +export function namespacedGraphQLTypeName(soapType: SoapType): string { + return `${simplifyNamespace(soapType.namespace)}_${capitalizeFirstLetter(soapType.name)}`; +} + +function simplifyNamespace(namespace: string): string { + return namespace + .toLowerCase() + .replace(/^https?:\/\/(www.)?/, '') + .replace(/[^a-z0-9]+/g, '_') + .replace(/[-_][a-z]/g, (group) => group.slice(-1).toUpperCase()); +} diff --git a/src/soap2graphql/schema-resolver.ts b/src/soap2graphql/schema-resolver.ts index 69b7aab..5a85249 100644 --- a/src/soap2graphql/schema-resolver.ts +++ b/src/soap2graphql/schema-resolver.ts @@ -1,50 +1,52 @@ import { SchemaOptions } from './soap2graphql'; import { - defaultOutputNameResolver, - defaultInterfaceNameResolver, defaultInputNameResolver, + defaultInterfaceNameResolver, + defaultOutputNameResolver, + defaultScalarNameResolver, } from './name-resolver'; import { DefaultTypeResolver } from './custom-type-resolver'; import { GraphQLSchemaConfig } from 'graphql/type/schema'; import { GraphQLString } from 'graphql/type/scalars'; import { + SoapComplexType, SoapEndpoint, - SoapService, - SoapPort, - SoapOperation, SoapField, + SoapOperation, + SoapPort, + SoapService, + SoapSimpleType, SoapType, - SoapObjectType, - SoapOperationArg, } from './soap-endpoint'; import { SoapCaller } from './soap-caller'; import { inspect } from 'util'; import { - GraphQLObjectType, - GraphQLFieldConfigMap, GraphQLFieldConfig, GraphQLFieldConfigArgumentMap, - GraphQLOutputType, + GraphQLFieldConfigMap, GraphQLFieldResolver, - GraphQLNonNull, - GraphQLResolveInfo, + GraphQLInputFieldConfigMap, + GraphQLInputObjectType, + GraphQLInputObjectTypeConfig, + GraphQLInputType, GraphQLInterfaceType, + GraphQLInterfaceTypeConfig, GraphQLList, - GraphQLScalarType, + GraphQLObjectType, GraphQLObjectTypeConfig, - GraphQLInterfaceTypeConfig, - GraphQLInputType, - GraphQLInputObjectType, - GraphQLInputObjectTypeConfig, - GraphQLInputFieldConfigMap, + GraphQLOutputType, + GraphQLResolveInfo, + GraphQLScalarType, } from 'graphql'; import { Logger } from './logger'; +import { GraphQLJSON } from 'graphql-scalars'; export class SchemaResolver { private readonly options: SchemaOptions; private outputResolver: GraphqlOutputFieldResolver = null; private inputResolver: GraphqlInputFieldResolver = null; + private scalarResolver: GraphQLScalarResolver; constructor( private soapEndpoint: SoapEndpoint, @@ -53,6 +55,7 @@ export class SchemaResolver { private logger: Logger, ) { this.options = this.defaultOptions(options); + this.scalarResolver = new GraphQLScalarResolver(this.options, this.logger); } defaultOptions(options: SchemaOptions) { @@ -67,6 +70,9 @@ export class SchemaResolver { if (!options.inputNameResolver) { options.inputNameResolver = defaultInputNameResolver; } + if (!options.scalarNameResolver) { + options.scalarNameResolver = defaultScalarNameResolver; + } if (!options.customResolver) { options.customResolver = new DefaultTypeResolver(); @@ -76,8 +82,16 @@ export class SchemaResolver { } resolve(): GraphQLSchemaConfig { - this.outputResolver = new GraphqlOutputFieldResolver(this.options, this.logger); - this.inputResolver = new GraphqlInputFieldResolver(this.options, this.logger); + this.outputResolver = new GraphqlOutputFieldResolver( + this.scalarResolver, + this.options, + this.logger, + ); + this.inputResolver = new GraphqlInputFieldResolver( + this.scalarResolver, + this.options, + this.logger, + ); return { query: this.createQueryObject(), @@ -203,11 +217,18 @@ export class SchemaResolver { createSoapOperationFieldArgs(operation: SoapOperation): GraphQLFieldConfigArgumentMap { const args: GraphQLFieldConfigArgumentMap = {}; - operation.args().forEach((soapField: SoapField) => { - args[soapField.name] = { - type: this.inputResolver.resolve(soapField), - }; - }); + const inputType = operation.inputType(); + switch (inputType.kind) { + case 'complexType': + inputType.fields?.forEach((soapField) => { + args[soapField.name] = { + type: this.inputResolver.resolve(soapField), + }; + }); + break; + case 'simpleType': + args['input'] = { type: this.scalarResolver.resolve(inputType) }; + } return args; } @@ -239,7 +260,11 @@ class GraphqlOutputFieldResolver { private alreadyResolvedOutputTypes: Map = new Map(); private alreadyResolvedInterfaceTypes: Map = new Map(); - constructor(private options: SchemaOptions, private logger: Logger) {} + constructor( + private scalarResolver: GraphQLScalarResolver, + private options: SchemaOptions, + private logger: Logger, + ) {} resolve(input: { type: SoapType; isList: boolean }): GraphQLOutputType { try { @@ -258,18 +283,21 @@ class GraphqlOutputFieldResolver { if (this.alreadyResolvedOutputTypes.has(soapType)) { return this.alreadyResolvedOutputTypes.get(soapType); } - - if (typeof soapType === 'string') { - const customType: GraphQLOutputType = this.options.customResolver.outputType(soapType); - if (!!customType) { - this.alreadyResolvedOutputTypes.set(soapType, customType); - return customType; - } + const customType: GraphQLOutputType = this.options.customResolver.outputType( + soapType.name, + soapType.namespace, + ); + if (!!customType) { + this.alreadyResolvedOutputTypes.set(soapType, customType); + return customType; + } + if (soapType.kind === 'simpleType') { + return this.scalarResolver.resolveScalar(soapType); } else { - const objectType: GraphQLObjectType = this.createObjectType(soapType); - if (!!objectType) { - this.alreadyResolvedOutputTypes.set(soapType, objectType); - return objectType; + const type: GraphQLObjectType | GraphQLScalarType = this.createObjectType(soapType); + if (!!type) { + this.alreadyResolvedOutputTypes.set(soapType, type); + return type; } } @@ -280,11 +308,15 @@ class GraphqlOutputFieldResolver { return GraphQLString; } - private createObjectType(soapType: SoapObjectType): GraphQLObjectType { + private createObjectType(soapType: SoapComplexType): GraphQLObjectType | GraphQLScalarType { + if (soapType.fields == undefined) { + // something has gone wrong during type resolution to be able to continue untyped in cases of sudden unavailability of xsd sources etc. + return GraphQLJSON; + } return new GraphQLObjectType(this.createObjectTypeConfig(soapType)); } - private createObjectTypeConfig(soapType: SoapObjectType): GraphQLObjectTypeConfig { + private createObjectTypeConfig(soapType: SoapComplexType): GraphQLObjectTypeConfig { const fields = (): GraphQLFieldConfigMap => { const fieldMap: GraphQLFieldConfigMap = {}; this.appendObjectTypeFields(fieldMap, soapType); @@ -312,19 +344,27 @@ class GraphqlOutputFieldResolver { private appendObjectTypeFields( fieldMap: GraphQLFieldConfigMap, - soapType: SoapObjectType, + soapType: SoapComplexType, ): void { this.appendTypeFields(fieldMap, soapType); } - private appendInterfaces(interfaces: GraphQLInterfaceType[], soapType: SoapObjectType): void { - if (!!soapType.base) { + private appendInterfaces(interfaces: GraphQLInterfaceType[], soapType: SoapComplexType): void { + // we only add interfaces with fields + if (!!soapType.base && this.hasInterfaceFieldsRecursive(soapType.base)) { interfaces.push(this.resolveInterfaceType(soapType.base)); this.appendInterfaces(interfaces, soapType.base); } } - private resolveInterfaceType(soapType: SoapObjectType): GraphQLInterfaceType { + private hasInterfaceFieldsRecursive(soapType: SoapComplexType) { + if (soapType.fields.length) { + return true; + } + return soapType.base && this.hasInterfaceFieldsRecursive(soapType.base); + } + + private resolveInterfaceType(soapType: SoapComplexType): GraphQLInterfaceType { if (this.alreadyResolvedInterfaceTypes.has(soapType)) { return this.alreadyResolvedInterfaceTypes.get(soapType); } @@ -334,12 +374,12 @@ class GraphqlOutputFieldResolver { return interfaceType; } - private createInterfaceType(soapType: SoapObjectType): GraphQLInterfaceType { + private createInterfaceType(soapType: SoapComplexType): GraphQLInterfaceType { return new GraphQLInterfaceType(this.createInterfaceTypeConfig(soapType)); } private createInterfaceTypeConfig( - soapType: SoapObjectType, + soapType: SoapComplexType, ): GraphQLInterfaceTypeConfig { const fields = (): GraphQLFieldConfigMap => { const fieldMap: GraphQLFieldConfigMap = {}; @@ -365,7 +405,7 @@ class GraphqlOutputFieldResolver { private appendInterfaceTypeFields( fieldMap: GraphQLFieldConfigMap, - soapType: SoapObjectType, + soapType: SoapComplexType, ): void { this.appendTypeFields(fieldMap, soapType); } @@ -385,10 +425,53 @@ class GraphqlOutputFieldResolver { } } +class GraphQLScalarResolver { + private alreadyResolved: Map = new Map(); + + constructor(private options: SchemaOptions, private logger: Logger) {} + + resolve(type: SoapSimpleType): GraphQLScalarType { + try { + return this.resolveScalar(type); + } catch (err) { + const errStacked = new Error( + `could not resolve scalar type for ${inspect(type.name, false, 4)}`, + ); + errStacked.stack += '\nCaused by: ' + err.stack; + throw errStacked; + } + } + + resolveScalar(soapType: SoapType): GraphQLScalarType { + if (this.alreadyResolved.has(soapType)) { + return this.alreadyResolved.get(soapType); + } + const type = this.createScalarType(soapType as SoapSimpleType); + if (!!type) { + this.alreadyResolved.set(soapType, type); + return type; + } + this.logger.warn(() => `could not resolve scalar type '${soapType}'; using GraphQLString`); + this.alreadyResolved.set(soapType, GraphQLString); + return GraphQLString; + } + + private createScalarType(soapType: SoapSimpleType): GraphQLScalarType { + return new GraphQLScalarType({ + ...soapType, + name: this.options.inputNameResolver(soapType), + }); + } +} + class GraphqlInputFieldResolver { private alreadyResolved: Map = new Map(); - constructor(private options: SchemaOptions, private logger: Logger) {} + constructor( + private scalarResolver: GraphQLScalarResolver, + private options: SchemaOptions, + private logger: Logger, + ) {} resolve(input: { type: SoapType; isList: boolean }): GraphQLInputType { try { @@ -407,19 +490,24 @@ class GraphqlInputFieldResolver { if (this.alreadyResolved.has(soapType)) { return this.alreadyResolved.get(soapType); } + const customType: GraphQLInputType = this.options.customResolver.inputType( + soapType.name, + soapType.namespace, + ); + if (!!customType) { + this.alreadyResolved.set(soapType, customType); + return customType; + } - if (typeof soapType === 'string') { - const customType: GraphQLInputType = this.options.customResolver.inputType(soapType); - if (!!customType) { - this.alreadyResolved.set(soapType, customType); - return customType; - } - } else { - const objectType: GraphQLInputObjectType = this.createObjectType(soapType); - if (!!objectType) { - this.alreadyResolved.set(soapType, objectType); - return objectType; - } + if (soapType.kind == 'simpleType') { + return this.scalarResolver.resolveScalar(soapType as SoapSimpleType); + } + const objectType: GraphQLInputObjectType | GraphQLScalarType = this.createObjectType( + soapType as SoapComplexType, + ); + if (!!objectType) { + this.alreadyResolved.set(soapType, objectType); + return objectType; } this.logger.warn(() => `could not resolve input type '${soapType}'; using GraphQLString`); @@ -427,11 +515,17 @@ class GraphqlInputFieldResolver { return GraphQLString; } - private createObjectType(soapType: SoapObjectType): GraphQLInputObjectType { + private createObjectType( + soapType: SoapComplexType, + ): GraphQLInputObjectType | GraphQLScalarType { + if (soapType.fields == undefined) { + // something has gone wrong during type resolution to be able to continue untyped in cases of sudden unavailability of xsd sources etc. + return GraphQLJSON; + } return new GraphQLInputObjectType(this.createObjectTypeConfig(soapType)); } - private createObjectTypeConfig(soapType: SoapObjectType): GraphQLInputObjectTypeConfig { + private createObjectTypeConfig(soapType: SoapComplexType): GraphQLInputObjectTypeConfig { const fields = (): GraphQLInputFieldConfigMap => { const fieldMap: GraphQLInputFieldConfigMap = {}; this.appendObjectTypeFields(fieldMap, soapType); @@ -452,7 +546,7 @@ class GraphqlInputFieldResolver { private appendObjectTypeFields( fieldMap: GraphQLInputFieldConfigMap, - soapType: SoapObjectType, + soapType: SoapComplexType, ): void { soapType.fields.forEach((soapField: SoapField) => { fieldMap[soapField.name] = { diff --git a/src/soap2graphql/soap-endpoint.ts b/src/soap2graphql/soap-endpoint.ts index 5f04e84..d822612 100644 --- a/src/soap2graphql/soap-endpoint.ts +++ b/src/soap2graphql/soap-endpoint.ts @@ -32,34 +32,37 @@ export interface SoapOperation { /** * Arguments that this operation accepts. */ - args(): SoapOperationArg[]; + inputType(): SoapType; /** * Output that this operation provides if called. */ output(): { type: SoapType; isList: boolean }; - /** - * The field in the SOAP output message that contains the actual output. - */ - resultField(): string; } /** * A type declared in the WSDL. */ -export type SoapType = SoapObjectType | SoapPrimitiveType; +export type SoapType = SoapComplexType | SoapSimpleType; /** - * A primitive type in the WSDL - only defined by its name. + * A primitive type in the WSDL. */ -export type SoapPrimitiveType = string; +export interface SoapSimpleType { + kind: 'simpleType'; + namespace: string; + name: string; + base?: SoapSimpleType; +} /** * An object type in the WSDL. * Defined by its name, fields and maybe a base type. */ -export interface SoapObjectType { +export interface SoapComplexType { + kind: 'complexType'; name: string; - base: SoapObjectType; + namespace: string; + base: SoapComplexType; fields: SoapField[]; } diff --git a/src/soap2graphql/soap2graphql.ts b/src/soap2graphql/soap2graphql.ts index 6027e51..a7c0c15 100644 --- a/src/soap2graphql/soap2graphql.ts +++ b/src/soap2graphql/soap2graphql.ts @@ -54,7 +54,7 @@ export type SchemaOptions = { */ outputNameResolver?: NameResolver; /** - * Function that proivides the name of a GraphQLInterfaceType. + * Function that provides the name of a GraphQLInterfaceType. * The created name must be unique in the GraphQL schema! * (This is especially important, since SOAP allows to use the same type for input and output, but GraphQL does not) * @@ -62,13 +62,21 @@ export type SchemaOptions = { */ interfaceNameResolver?: NameResolver; /** - * Function that proivides the name of a GraphQLInputType. + * Function that provides the name of a GraphQLInputType. * The created name must be unique in the GraphQL schema! * (This is especially important, since SOAP allows to use the same type for input and output, but GraphQL does not) * * default: WSDL type name + 'Input'. */ inputNameResolver?: NameResolver; + /** + * Function that provides the name of a GraphQLScalarType. + * The created name must be unique in the GraphQL schema! + * (This is especially important, since SOAP allows to use the same type for input and output, but GraphQL does not) + * + * default: WSDL type name + 'Input'. + */ + scalarNameResolver?: NameResolver; }; export function createSchemaConfig( From d91d05a7b0b9a2484282fa9e9236cd8b156e55ea Mon Sep 17 00:00:00 2001 From: Sebastian Henke Date: Mon, 24 Jul 2023 10:59:57 +0200 Subject: [PATCH 3/6] fix: prevent generating duplicate types --- src/node-soap/node-soap-resolver.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/node-soap/node-soap-resolver.ts b/src/node-soap/node-soap-resolver.ts index 41c3735..44020a4 100644 --- a/src/node-soap/node-soap-resolver.ts +++ b/src/node-soap/node-soap-resolver.ts @@ -160,14 +160,18 @@ export class NodeSoapWsdlResolver { if (schemaObject.$type) { const ns = resolveNamespace(schemaObject); const type = this.findXsdTypeDefinition(ns, schemaObject.$type); - return this.resolveWsdlNameToSoapType( + const soapType = this.resolveWsdlNameToSoapType( ns, withoutNamespace(schemaObject.$lookupType), `return type of element '${schemaObject.$name}`, ); + this.alreadyResolved.set(soapType.namespace + soapType.name, soapType); + return soapType; } else { // must be anonymous - return this.resolveAnonymousTypeToSoapType(schemaObject, schemaObject.$name); + const soapType = this.resolveAnonymousTypeToSoapType(schemaObject, schemaObject.$name); + this.alreadyResolved.set(soapType.namespace + soapType.name, soapType); + return soapType; } default: soapType = XS_STRING; From 1d9990472047436e48f64abfff08201848151bd1 Mon Sep 17 00:00:00 2001 From: Sebastian Henke Date: Mon, 24 Jul 2023 11:52:19 +0200 Subject: [PATCH 4/6] feature: support xsd attributes (except attributeGroup) --- dev/index.ts | 4 +- src/node-soap/node-soap-resolver.ts | 100 +++++++++++++++++++--------- src/node-soap/node-soap.ts | 6 +- src/soap-graphql.ts | 7 +- src/soap2graphql/name-resolver.ts | 2 + src/soap2graphql/schema-resolver.ts | 68 ++++++++++++++++--- src/soap2graphql/soap-endpoint.ts | 6 +- src/soap2graphql/soap2graphql.ts | 7 ++ 8 files changed, 157 insertions(+), 43 deletions(-) diff --git a/dev/index.ts b/dev/index.ts index 44eb140..da363f8 100644 --- a/dev/index.ts +++ b/dev/index.ts @@ -5,11 +5,13 @@ import * as http from 'http'; // http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL // http://www.dataaccess.com/webservicesserver/numberconversion.wso?WSDL // https://www.uid-wse.admin.ch/V5.0/PublicServices.svc?WSDL +// https://ws.ecotransit.org/ETWStandard?wsdl +// https://ec.europa.eu/taxation_customs/dds2/eos/validation/services/validation?wsdl soapGraphqlSchema({ debug: true, createClient: { - url: 'http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL', + url: 'https://ws.ecotransit.org/ETWStandard?wsdl', }, }).then((schema) => { const handler = createHandler({ schema }); diff --git a/src/node-soap/node-soap-resolver.ts b/src/node-soap/node-soap-resolver.ts index 44020a4..c5e53fa 100644 --- a/src/node-soap/node-soap-resolver.ts +++ b/src/node-soap/node-soap-resolver.ts @@ -1,4 +1,5 @@ import { + SoapAttribute, SoapComplexType, SoapField, SoapSimpleType, @@ -146,6 +147,7 @@ export class NodeSoapWsdlResolver { namespace: namespace, base: null, fields: null, + attributes: null, }; break; case SimpleTypeElement: @@ -169,7 +171,10 @@ export class NodeSoapWsdlResolver { return soapType; } else { // must be anonymous - const soapType = this.resolveAnonymousTypeToSoapType(schemaObject, schemaObject.$name); + const soapType = this.resolveAnonymousTypeToSoapType( + schemaObject, + schemaObject.$name, + ); this.alreadyResolved.set(soapType.namespace + soapType.name, soapType); return soapType; } @@ -228,6 +233,7 @@ export class NodeSoapWsdlResolver { namespace: xsdFieldDefinition.$targetNamespace, base: null, fields: null, + attributes: null, }; // resolve bindings (field types, base type) @@ -283,6 +289,9 @@ export class NodeSoapWsdlResolver { let baseTypeName: string = null; const body: Element = typeDefinition.children[0]; + const attributes: Element[] = typeDefinition.children.filter( + (t) => t.nsName == 'xs:attribute', + ); switch (body?.constructor) { case SequenceElement: fields = this.extractChildren(body as SequenceElement); @@ -318,36 +327,9 @@ export class NodeSoapWsdlResolver { fields = undefined; } - // todo extract if (soapType.kind === 'complexType' && fields) { - const soapFields: SoapField[] = fields.map((field: ElementElement) => { - let type; - if (field.$type) { - const ns = resolveNamespace(field); - type = this.resolveWsdlNameToSoapType( - ns, - withoutNamespace(field.$type), - `field '${field.$name}' of soap type '${soapType.name}'`, - ); - } else if (field.children.length) { - let generatedTypeName = `${soapType.name}_${capitalizeFirstLetter( - field.$name, - )}`; - type = this.resolveAnonymousTypeToSoapType(field, generatedTypeName); - } else { - this.warn( - () => - `Impossible to generate type for field without type and without children, using string for '${field.$name}'.`, - ); - type = XS_STRING; - } - return { - name: field.$name, - type, - isList: getIsList(field), - }; - }); - soapType.fields = soapFields; + soapType.fields = this.resolveSoapFields(fields, soapType); + soapType.attributes = this.resolveSoapAttributes(attributes, soapType); } const baseType: SoapComplexType = !baseTypeName @@ -363,6 +345,64 @@ export class NodeSoapWsdlResolver { soapType.base = baseType; } + private resolveSoapFields(fieldElements: Element[], soapType: SoapComplexType) { + const fields = fieldElements.map((field: ElementElement) => { + let type; + if (field.$type) { + const ns = resolveNamespace(field); + type = this.resolveWsdlNameToSoapType( + ns, + withoutNamespace(field.$type), + `field '${field.$name}' of soap type '${soapType.name}'`, + ); + } else if (field.children.length) { + let generatedTypeName = `${soapType.name}_${capitalizeFirstLetter(field.$name)}`; + type = this.resolveAnonymousTypeToSoapType(field, generatedTypeName); + } else { + this.warn( + () => + `Impossible to generate type for field without type and without children, using string for '${field.$name}'.`, + ); + type = XS_STRING; + } + return { + name: field.$name, + type, + isList: getIsList(field), + }; + }); + return fields; + } + + private resolveSoapAttributes( + attributeElements: Element[], + soapType: SoapComplexType, + ): SoapAttribute[] { + const attributes = attributeElements.map((attribute: ElementElement) => { + let type; + if (attribute.$type) { + const ns = resolveNamespace(attribute); + type = this.resolveWsdlNameToSoapType( + ns, + withoutNamespace(attribute.$type), + `field '${attribute.$name}' of soap type '${soapType.name}'`, + ); + } else { + this.warn( + () => + `Impossible to generate type for attribute without type and without children, using string for '${attribute.$name}'.`, + ); + type = XS_STRING; + } + return { + name: attribute.$name, + type, + isList: false, + }; + }); + return attributes; + } + private extractChildren(sequence: SequenceElement): Element[] { const result: Element[] = []; sequence.children.forEach((child) => { diff --git a/src/node-soap/node-soap.ts b/src/node-soap/node-soap.ts index 6c6a73a..032ea51 100644 --- a/src/node-soap/node-soap.ts +++ b/src/node-soap/node-soap.ts @@ -1,4 +1,5 @@ import { createClient, BasicAuthSecurity, Client, IOptions, WSDL } from 'soap'; +import { defaultAttributesKey } from '../soap2graphql/name-resolver'; /** * Type for the soap-client from node-soap. @@ -25,8 +26,11 @@ export type NodeSoapOptions = { export async function createSoapClient( url: string, options: NodeSoapOptions = {}, + attributesKey: string, ): Promise { - const opts: IOptions = !options.options ? {} : options.options; + const opts: IOptions = !options.options + ? { attributesKey } + : { ...options.options, attributesKey }; return new Promise((resolve, reject) => { try { createClient(url, opts, (err: any, client: Client) => { diff --git a/src/soap-graphql.ts b/src/soap-graphql.ts index 3cb5dc3..320d04b 100644 --- a/src/soap-graphql.ts +++ b/src/soap-graphql.ts @@ -6,6 +6,7 @@ import { SoapEndpoint } from './soap2graphql/soap-endpoint'; import { createSoapEndpoint, NodeSoapPort } from './node-soap/node-soap-endpoint'; import { createLogger, Logger } from './soap2graphql/logger'; import { NodeSoapCaller } from './node-soap/node-soap-caller'; +import { defaultAttributesKey } from './soap2graphql/name-resolver'; export type SoapGraphqlOptions = { /** @@ -99,7 +100,11 @@ async function useSoapClient(options: SoapGraphqlOptions): Promise { - args[soapField.name] = { - type: this.inputResolver.resolve(soapField), - }; - }); - break; + const gqlInputType = this.inputResolver.resolve({ + type: operation.inputType(), + }) as GraphQLInputObjectType; + return gqlInputType.getFields(); case 'simpleType': args['input'] = { type: this.scalarResolver.resolve(inputType) }; } @@ -266,7 +270,7 @@ class GraphqlOutputFieldResolver { private logger: Logger, ) {} - resolve(input: { type: SoapType; isList: boolean }): GraphQLOutputType { + resolve(input: { type: SoapType; isList?: boolean }): GraphQLOutputType { try { const type: GraphQLOutputType = this.resolveOutputType(input.type); return input.isList ? new GraphQLList(type) : type; @@ -320,6 +324,7 @@ class GraphqlOutputFieldResolver { const fields = (): GraphQLFieldConfigMap => { const fieldMap: GraphQLFieldConfigMap = {}; this.appendObjectTypeFields(fieldMap, soapType); + this.appendAttributesType(fieldMap, soapType); if (Object.keys(fieldMap).length == 0) { fieldMap['dummy'] = { type: GraphQLString, @@ -412,7 +417,7 @@ class GraphqlOutputFieldResolver { private appendTypeFields( fieldMap: GraphQLFieldConfigMap, - soapType: SoapObjectType, + soapType: SoapComplexType, ): void { soapType.fields.forEach((soapField: SoapField) => { fieldMap[soapField.name] = { @@ -423,6 +428,28 @@ class GraphqlOutputFieldResolver { this.appendTypeFields(fieldMap, soapType.base); } } + + private appendAttributesType( + fieldMap: GraphQLFieldConfigMap, + soapType: SoapComplexType, + ): void { + if (soapType.attributes?.length) { + const attributesFieldMap: GraphQLFieldConfigMap = {}; + soapType.attributes.forEach((soapAttribute: SoapAttribute) => { + attributesFieldMap[soapAttribute.name] = { + type: this.resolve(soapAttribute), + }; + }); + fieldMap[this.options.attributesKey] = { + description: `Attribute values for '${soapType.name}'`, + type: new GraphQLObjectType({ + name: `${soapType.name}_Attributes`, + description: `Attribute values for '${soapType.name}'`, + fields: attributesFieldMap, + }), + }; + } + } } class GraphQLScalarResolver { @@ -473,7 +500,7 @@ class GraphqlInputFieldResolver { private logger: Logger, ) {} - resolve(input: { type: SoapType; isList: boolean }): GraphQLInputType { + resolve(input: { type: SoapType; isList?: boolean }): GraphQLInputType { try { const type: GraphQLInputType = this.resolveInputType(input.type); return input.isList ? new GraphQLList(type) : type; @@ -529,6 +556,7 @@ class GraphqlInputFieldResolver { const fields = (): GraphQLInputFieldConfigMap => { const fieldMap: GraphQLInputFieldConfigMap = {}; this.appendObjectTypeFields(fieldMap, soapType); + this.appendAttributesType(fieldMap, soapType); if (Object.keys(fieldMap).length == 0) { fieldMap['dummy'] = { type: GraphQLString, @@ -557,4 +585,26 @@ class GraphqlInputFieldResolver { this.appendObjectTypeFields(fieldMap, soapType.base); } } + + private appendAttributesType( + fieldMap: GraphQLInputFieldConfigMap, + soapType: SoapComplexType, + ): void { + if (soapType.attributes?.length) { + const attributesFieldMap: GraphQLInputFieldConfigMap = {}; + soapType.attributes.forEach((soapAttribute: SoapAttribute) => { + attributesFieldMap[soapAttribute.name] = { + type: this.resolve(soapAttribute), + }; + }); + fieldMap[this.options.attributesKey] = { + description: `Attribute values for '${soapType.name}'`, + type: new GraphQLInputObjectType({ + name: `${soapType.name}Input_Attributes`, + description: `Attribute values for '${soapType.name}'`, + fields: attributesFieldMap, + }), + }; + } + } } diff --git a/src/soap2graphql/soap-endpoint.ts b/src/soap2graphql/soap-endpoint.ts index d822612..d18a7bb 100644 --- a/src/soap2graphql/soap-endpoint.ts +++ b/src/soap2graphql/soap-endpoint.ts @@ -64,6 +64,7 @@ export interface SoapComplexType { namespace: string; base: SoapComplexType; fields: SoapField[]; + attributes: SoapAttribute[]; } export interface SoapField { @@ -72,4 +73,7 @@ export interface SoapField { isList: boolean; } -export type SoapOperationArg = SoapField; +export interface SoapAttribute { + name: string; + type: SoapSimpleType; +} diff --git a/src/soap2graphql/soap2graphql.ts b/src/soap2graphql/soap2graphql.ts index a7c0c15..eb96f67 100644 --- a/src/soap2graphql/soap2graphql.ts +++ b/src/soap2graphql/soap2graphql.ts @@ -77,6 +77,13 @@ export type SchemaOptions = { * default: WSDL type name + 'Input'. */ scalarNameResolver?: NameResolver; + + /** + * default field name for attributes of complex type elements. + * + * default: __attributes + */ + attributesKey?: string; }; export function createSchemaConfig( From 0d04947e8e0ff392b9e8b45bad1d45e407d81ace Mon Sep 17 00:00:00 2001 From: Peter Schroeder Date: Fri, 15 Sep 2023 08:25:04 +0200 Subject: [PATCH 5/6] rework README --- README.md | 41 ++++++++--------------------------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 91f5143..b02c87a 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,19 @@ Create a GraphQL schema from a WSDL-defined SOAP endpoint. The created GraphQL schema contains all types declared in the WSDL and provides all operations of the SOAP endpoint as GraphQL mutation fields. This enables you to re-publish a SOAP endpoint as a GraphQL server. This might be convenient for clients, that already handle GraphQL and do not want to -handle SOAP. But note: The existence of this package should not necessarily encourage you to do this -... but it is possible. +handle SOAP. This package is fully dependend on the [node-soap package](https://github.com/vpulim/node-soap). It will only work in a Node.js environment. -Checkout [soap-graphql-demo](https://github.com/sevenclev/node-soap-graphql-demo) for a quick demo. +## Usage + +Main entry point is the function +`soapGraphqlSchema(options: SoapGraphqlOptions | string): Promise` + +See code comments for more details. -### Example (TypeScript) +### Example ```typescript import * as express from 'express'; @@ -36,32 +40,3 @@ soapGraphqlSchema('http://<>').then((schema: GraphQLSchema) => { }); }); ``` - -## Usage - -Main entry point is the function -`soapGraphqlSchema(options: SoapGraphqlOptions | string): Promise` - -See code comments for more details. - -## Limitations and Issues - -### Supported WSDLs - -There is no guarantee that this package will work with every valid WSDL. - -[node-soap-graphql.spec](spec/node-soap-graphql.spec.ts) lists SOAP endpoints that were tested with -this package. It also shows how to configure custom behavior for SOAP endpoints. - -Feel free to post an issue (or better yet: create a pull request with a test case) if this package -does not work with your SOAP endpoint. - -### XSD features - -WSDL, and especially the XSD-based schema section, -[allows a wide variety of options to define primitive types](https://www.w3.org/TR/xmlschema-2/#built-in-datatypes). -Handling of these options are only implemented in the most basic way; -[see `DefaultTypeResolver`](src/soap2graphql/custom-type-resolver.ts). - -In most cases you can handle the specifics of your SOAP endpoint by implementing a -[`CustomTypeResolver`](src/soap2graphql/custom-type-resolver.ts). From aed8188925415cbeff14547a105bbcb46142ab35 Mon Sep 17 00:00:00 2001 From: Peter Schroeder Date: Fri, 15 Sep 2023 08:45:50 +0200 Subject: [PATCH 6/6] fix specs --- .../default-type-resolver.spec.ts | 92 +++++++++---------- spec/src/soap2graphql/name-resolver.spec.ts | 66 +++++++------ 2 files changed, 85 insertions(+), 73 deletions(-) diff --git a/spec/src/soap2graphql/default-type-resolver.spec.ts b/spec/src/soap2graphql/default-type-resolver.spec.ts index 936e9b6..56efe08 100644 --- a/spec/src/soap2graphql/default-type-resolver.spec.ts +++ b/spec/src/soap2graphql/default-type-resolver.spec.ts @@ -18,58 +18,58 @@ describe('DefaultTypeResolver', () => { it('resolves default scalar types', async () => { const resolver = new DefaultTypeResolver(); - expect(resolver.resolve(null)).to.not.exist; - expect(resolver.resolve(undefined)).to.not.exist; + expect(resolver.resolve(null, '')).to.not.exist; + expect(resolver.resolve(undefined, '')).to.not.exist; - expect(resolver.resolve('lalal')).to.not.exist; + expect(resolver.resolve('lalal', '')).to.not.exist; - expect(resolver.resolve('string')).to.equal(GraphQLString); - expect(resolver.resolve('base64Binary')).to.equal(GraphQLString); - expect(resolver.resolve('hexBinary')).to.equal(GraphQLString); - expect(resolver.resolve('duration')).to.equal(GraphQLString); - expect(resolver.resolve('gYearMonth')).to.equal(GraphQLString); - expect(resolver.resolve('gYear')).to.equal(GraphQLString); - expect(resolver.resolve('gMonthDay')).to.equal(GraphQLString); - expect(resolver.resolve('gDay')).to.equal(GraphQLString); - expect(resolver.resolve('gMonth')).to.equal(GraphQLString); - expect(resolver.resolve('anyURI')).to.equal(GraphQLString); - expect(resolver.resolve('QName')).to.equal(GraphQLString); - expect(resolver.resolve('normalizedString')).to.equal(GraphQLString); - expect(resolver.resolve('token')).to.equal(GraphQLString); - expect(resolver.resolve('NMTOKEN')).to.equal(GraphQLString); - expect(resolver.resolve('NMTOKENS')).to.equal(GraphQLString); - expect(resolver.resolve('language')).to.equal(GraphQLString); - expect(resolver.resolve('Name')).to.equal(GraphQLString); - expect(resolver.resolve('NCName')).to.equal(GraphQLString); - expect(resolver.resolve('IDREF')).to.equal(GraphQLString); - expect(resolver.resolve('IDREFS')).to.equal(GraphQLString); - expect(resolver.resolve('ENTITY')).to.equal(GraphQLString); - expect(resolver.resolve('ENTITIES')).to.equal(GraphQLString); + expect(resolver.resolve('string', '')).to.equal(GraphQLString); + expect(resolver.resolve('base64Binary', '')).to.equal(GraphQLString); + expect(resolver.resolve('hexBinary', '')).to.equal(GraphQLString); + expect(resolver.resolve('duration', '')).to.equal(GraphQLString); + expect(resolver.resolve('gYearMonth', '')).to.equal(GraphQLString); + expect(resolver.resolve('gYear', '')).to.equal(GraphQLString); + expect(resolver.resolve('gMonthDay', '')).to.equal(GraphQLString); + expect(resolver.resolve('gDay', '')).to.equal(GraphQLString); + expect(resolver.resolve('gMonth', '')).to.equal(GraphQLString); + expect(resolver.resolve('anyURI', '')).to.equal(GraphQLString); + expect(resolver.resolve('QName', '')).to.equal(GraphQLString); + expect(resolver.resolve('normalizedString', '')).to.equal(GraphQLString); + expect(resolver.resolve('token', '')).to.equal(GraphQLString); + expect(resolver.resolve('NMTOKEN', '')).to.equal(GraphQLString); + expect(resolver.resolve('NMTOKENS', '')).to.equal(GraphQLString); + expect(resolver.resolve('language', '')).to.equal(GraphQLString); + expect(resolver.resolve('Name', '')).to.equal(GraphQLString); + expect(resolver.resolve('NCName', '')).to.equal(GraphQLString); + expect(resolver.resolve('IDREF', '')).to.equal(GraphQLString); + expect(resolver.resolve('IDREFS', '')).to.equal(GraphQLString); + expect(resolver.resolve('ENTITY', '')).to.equal(GraphQLString); + expect(resolver.resolve('ENTITIES', '')).to.equal(GraphQLString); - expect(resolver.resolve('ID')).to.equal(GraphQLID); + expect(resolver.resolve('ID', '')).to.equal(GraphQLID); - expect(resolver.resolve('boolean')).to.equal(GraphQLBoolean); + expect(resolver.resolve('boolean', '')).to.equal(GraphQLBoolean); - expect(resolver.resolve('byte')).to.equal(GraphQLInt); - expect(resolver.resolve('unsignedByte')).to.equal(GraphQLInt); - expect(resolver.resolve('short')).to.equal(GraphQLInt); - expect(resolver.resolve('unsignedShort')).to.equal(GraphQLInt); - expect(resolver.resolve('int')).to.equal(GraphQLInt); - expect(resolver.resolve('unsignedInt')).to.equal(GraphQLInt); - expect(resolver.resolve('integer')).to.equal(GraphQLInt); - expect(resolver.resolve('positiveInteger')).to.equal(GraphQLInt); - expect(resolver.resolve('nonPositiveInteger')).to.equal(GraphQLInt); - expect(resolver.resolve('negativeInteger')).to.equal(GraphQLInt); - expect(resolver.resolve('nonNegativeInteger')).to.equal(GraphQLInt); - expect(resolver.resolve('long')).to.equal(GraphQLInt); - expect(resolver.resolve('unsignedLong')).to.equal(GraphQLInt); + expect(resolver.resolve('byte', '')).to.equal(GraphQLInt); + expect(resolver.resolve('unsignedByte', '')).to.equal(GraphQLInt); + expect(resolver.resolve('short', '')).to.equal(GraphQLInt); + expect(resolver.resolve('unsignedShort', '')).to.equal(GraphQLInt); + expect(resolver.resolve('int', '')).to.equal(GraphQLInt); + expect(resolver.resolve('unsignedInt', '')).to.equal(GraphQLInt); + expect(resolver.resolve('integer', '')).to.equal(GraphQLInt); + expect(resolver.resolve('positiveInteger', '')).to.equal(GraphQLInt); + expect(resolver.resolve('nonPositiveInteger', '')).to.equal(GraphQLInt); + expect(resolver.resolve('negativeInteger', '')).to.equal(GraphQLInt); + expect(resolver.resolve('nonNegativeInteger', '')).to.equal(GraphQLInt); + expect(resolver.resolve('long', '')).to.equal(GraphQLInt); + expect(resolver.resolve('unsignedLong', '')).to.equal(GraphQLInt); - expect(resolver.resolve('decimal')).to.equal(GraphQLFloat); - expect(resolver.resolve('float')).to.equal(GraphQLFloat); - expect(resolver.resolve('double')).to.equal(GraphQLFloat); + expect(resolver.resolve('decimal', '')).to.equal(GraphQLFloat); + expect(resolver.resolve('float', '')).to.equal(GraphQLFloat); + expect(resolver.resolve('double', '')).to.equal(GraphQLFloat); - expect(resolver.resolve('dateTime')).to.equal(GraphQLDateTime); - expect(resolver.resolve('date')).to.equal(GraphQLDate); - expect(resolver.resolve('time')).to.equal(GraphQLTime); + expect(resolver.resolve('dateTime', '')).to.equal(GraphQLDateTime); + expect(resolver.resolve('date', '')).to.equal(GraphQLDate); + expect(resolver.resolve('time', '')).to.equal(GraphQLTime); }).timeout(10000); }); diff --git a/spec/src/soap2graphql/name-resolver.spec.ts b/spec/src/soap2graphql/name-resolver.spec.ts index 24cb834..fc80048 100644 --- a/spec/src/soap2graphql/name-resolver.spec.ts +++ b/spec/src/soap2graphql/name-resolver.spec.ts @@ -14,51 +14,63 @@ describe('name-resolver', () => { it('defaultOutputNameResolver', async () => { expect(defaultOutputNameResolver(null)).to.not.exist; expect(defaultOutputNameResolver(undefined)).to.not.exist; - expect(defaultOutputNameResolver({ name: null } as any)).to.not.exist; - expect(defaultOutputNameResolver({ name: undefined } as any)).to.not.exist; - expect(defaultOutputNameResolver({ name: '' } as any)).to.not.exist; + expect(defaultOutputNameResolver({ name: null, namespace: 'N' } as any)).to.not.exist; + expect(defaultOutputNameResolver({ name: undefined, namespace: 'N' } as any)).to.not.exist; + expect(defaultOutputNameResolver({ name: '', namespace: 'N' } as any)).to.not.exist; - expect(defaultOutputNameResolver({ name: 'a' } as any)).to.equal('A'); - expect(defaultOutputNameResolver({ name: 'A' } as any)).to.equal('A'); + expect(defaultOutputNameResolver({ name: 'a', namespace: '' } as any)).to.equal('_A'); + expect(defaultOutputNameResolver({ name: 'A', namespace: '' } as any)).to.equal('_A'); - expect(defaultOutputNameResolver({ name: 'ab' } as any)).to.equal('Ab'); - expect(defaultOutputNameResolver({ name: 'Ab' } as any)).to.equal('Ab'); + expect(defaultOutputNameResolver({ name: 'a', namespace: 'n' } as any)).to.equal('N_A'); + expect(defaultOutputNameResolver({ name: 'A', namespace: 'N' } as any)).to.equal('N_A'); - expect(defaultOutputNameResolver({ name: 'abc' } as any)).to.equal('Abc'); - expect(defaultOutputNameResolver({ name: 'Abc' } as any)).to.equal('Abc'); + expect(defaultOutputNameResolver({ name: 'ab', namespace: 'N' } as any)).to.equal('N_Ab'); + expect(defaultOutputNameResolver({ name: 'Ab', namespace: 'N' } as any)).to.equal('N_Ab'); + + expect(defaultOutputNameResolver({ name: 'abc', namespace: 'N' } as any)).to.equal('N_Abc'); + expect(defaultOutputNameResolver({ name: 'Abc', namespace: 'N' } as any)).to.equal('N_Abc'); }).timeout(10000); it('defaultInterfaceNameResolver', async () => { expect(defaultInterfaceNameResolver(null)).to.not.exist; expect(defaultInterfaceNameResolver(undefined)).to.not.exist; - expect(defaultInterfaceNameResolver({ name: null } as any)).to.not.exist; - expect(defaultInterfaceNameResolver({ name: undefined } as any)).to.not.exist; - expect(defaultOutputNameResolver({ name: '' } as any)).to.not.exist; + expect(defaultInterfaceNameResolver({ name: null, namespace: 'N' } as any)).to.not.exist; + expect(defaultInterfaceNameResolver({ name: undefined, namespace: 'N' } as any)).to.not + .exist; + expect(defaultOutputNameResolver({ name: '', namespace: 'N' } as any)).to.not.exist; - expect(defaultInterfaceNameResolver({ name: 'a' } as any)).to.equal('iA'); - expect(defaultInterfaceNameResolver({ name: 'A' } as any)).to.equal('iA'); + expect(defaultInterfaceNameResolver({ name: 'a', namespace: 'N' } as any)).to.equal('iA'); + expect(defaultInterfaceNameResolver({ name: 'A', namespace: 'N' } as any)).to.equal('iA'); - expect(defaultInterfaceNameResolver({ name: 'ab' } as any)).to.equal('iAb'); - expect(defaultInterfaceNameResolver({ name: 'Ab' } as any)).to.equal('iAb'); + expect(defaultInterfaceNameResolver({ name: 'ab', namespace: 'N' } as any)).to.equal('iAb'); + expect(defaultInterfaceNameResolver({ name: 'Ab', namespace: 'N' } as any)).to.equal('iAb'); - expect(defaultInterfaceNameResolver({ name: 'abc' } as any)).to.equal('iAbc'); - expect(defaultInterfaceNameResolver({ name: 'Abc' } as any)).to.equal('iAbc'); + expect(defaultInterfaceNameResolver({ name: 'abc', namespace: 'N' } as any)).to.equal( + 'iAbc', + ); + expect(defaultInterfaceNameResolver({ name: 'Abc', namespace: 'N' } as any)).to.equal( + 'iAbc', + ); }).timeout(10000); it('defaultInputNameResolver', async () => { expect(defaultInputNameResolver(null)).to.not.exist; expect(defaultInputNameResolver(undefined)).to.not.exist; - expect(defaultInputNameResolver({ name: null } as any)).to.not.exist; - expect(defaultInputNameResolver({ name: undefined } as any)).to.not.exist; - expect(defaultInputNameResolver({ name: '' } as any)).to.not.exist; + expect(defaultInputNameResolver({ name: null, namespace: 'N' } as any)).to.not.exist; + expect(defaultInputNameResolver({ name: undefined, namespace: 'N' } as any)).to.not.exist; + expect(defaultInputNameResolver({ name: '', namespace: 'N' } as any)).to.not.exist; - expect(defaultInputNameResolver({ name: 'a' } as any)).to.equal('AInput'); - expect(defaultInputNameResolver({ name: 'A' } as any)).to.equal('AInput'); + expect(defaultInputNameResolver({ name: 'a', namespace: 'N' } as any)).to.equal('N_AInput'); + expect(defaultInputNameResolver({ name: 'A', namespace: 'N' } as any)).to.equal('N_AInput'); - expect(defaultInputNameResolver({ name: 'ab' } as any)).to.equal('AbInput'); - expect(defaultInputNameResolver({ name: 'Ab' } as any)).to.equal('AbInput'); + expect(defaultInputNameResolver({ name: 'ab', namespace: 'N' } as any)).to.equal('N_AbInput'); + expect(defaultInputNameResolver({ name: 'Ab', namespace: 'N' } as any)).to.equal('N_AbInput'); - expect(defaultInputNameResolver({ name: 'abc' } as any)).to.equal('AbcInput'); - expect(defaultInputNameResolver({ name: 'Abc' } as any)).to.equal('AbcInput'); + expect(defaultInputNameResolver({ name: 'abc', namespace: 'N' } as any)).to.equal( + 'N_AbcInput', + ); + expect(defaultInputNameResolver({ name: 'Abc', namespace: 'N' } as any)).to.equal( + 'N_AbcInput', + ); }).timeout(10000); });