diff --git a/packages/contract-cli/src/ast/jsdoc.js b/packages/contract-cli/src/ast/jsdoc.js index 068b66ffcf0b34e53e24a6227a9f7a57c987451c..1b0e07df192b96a354eec756e6d2e7dfc0c3638f 100644 --- a/packages/contract-cli/src/ast/jsdoc.js +++ b/packages/contract-cli/src/ast/jsdoc.js @@ -132,6 +132,12 @@ const processParams = function(params) { if (params && Array.isArray(params)) { return params.map(param => { // always return array from now on + // @TODO if the jsdoc is wrong (the problem was cause by without a type) + // then there is no names field, how do we notify the user about the correct + // error? + if (!param.type || !param.type.names) { + throw new Error(`Please check your documentation is correct or not!`) + } param.type = param.type.names.map(normalizeType) // pass through return param; @@ -146,7 +152,7 @@ const processParams = function(params) { * @return {*} false on nothing */ const getParams = res => ( - !!res.params ? ( foldParams( processParams(res.params) ) || false ) + !!res.params ? ( foldParams( processParams(res.params) ) || false ) : ( res.undocumented ? false : [] ) ) diff --git a/packages/resolver/package.json b/packages/resolver/package.json index fadea0938e8570d28af3d6434729b111923bf21c..9bfc5c2da697ff4dbab608a068d648922d2ba049 100644 --- a/packages/resolver/package.json +++ b/packages/resolver/package.json @@ -1,6 +1,6 @@ { "name": "jsonql-resolver", - "version": "0.8.7", + "version": "0.9.0", "description": "This is NOT for general use, please do not install it directly. This module is part of the jsonql tools supporting modules.", "main": "index.js", "files": [ @@ -9,8 +9,9 @@ ], "scripts": { "test": "ava --verbose", + "prepare": "npm run test", "test:clients": "DEBUG=jsonql* ava --verbose ./tests/clients.test.js", - "test:throw": "DEBUG=jsonql* ava --verbose ./tests/throw.test.js", + "test:throw": "DEBUG=jsonql-resolver* ava --verbose ./tests/throw.test.js", "contract": "DEBUG=jsonql-contract* jsonql-contract create ./tests/fixtures/resolvers ./tests/fixtures/contract" }, "keywords": [ @@ -35,7 +36,7 @@ }, "devDependencies": { "ava": "^2.4.0", - "jsonql-contract": "^1.7.18", + "jsonql-contract": "^1.7.19", "jsonql-koa": "^1.3.9", "server-io-core": "^1.2.0" }, diff --git a/packages/resolver/src/provide-node-clients.js b/packages/resolver/src/provide-node-clients.js index 7ffc907d098ed938198ac0e3c4242e97b6fc4010..1275e65a3856f512bbde5d06cc135c902e29c844 100644 --- a/packages/resolver/src/provide-node-clients.js +++ b/packages/resolver/src/provide-node-clients.js @@ -15,25 +15,29 @@ let hasClientConfig; * @param {object} config configuration * @return {function} the resolver with injection if any */ -async function provideNodeClients(resolver, config) { - if (hasClientConfig === false) { - debug(`nothing to inject`) - return resolver; // nothing to do - } - // if there is cache clients - if (clients.length) { - debug(`inject client from cache`) - return injectNodeClient(resolver, clients) - } - hasClientConfig = validateClientConfig(config) - if (hasClientConfig === false) { - debug(`check and nothing to inject`, config) - return resolver; // nothing to do - } - debug(`run init clients`) - // run init client once - clients = await clientsGenerator(hasClientConfig) - return injectNodeClient(resolver, clients) +function provideNodeClients(resolver, config) { + return new Promise(resolve => { + if (hasClientConfig === false) { + debug(`nothing to inject`) + return resolve(resolver) // nothing to do + } + // if there is cache clients + if (clients.length) { + debug(`inject client from cache`) + return resolve(injectNodeClient(resolver, clients)) + } + hasClientConfig = validateClientConfig(config) + if (hasClientConfig === false) { + debug(`check and nothing to inject`, config) + return resolve(resolver) // nothing to do + } + debug(`run init clients`) + // run init client once + clientsGenerator(hasClientConfig) + .then(clients => { + resolve(injectNodeClient(resolver, clients)) + }) + }) } module.exports = { provideNodeClients } diff --git a/packages/resolver/src/resolve-methods.js b/packages/resolver/src/resolve-methods.js index 9df112cfdbb7524527b220d6cb2d930887ee3a6c..ed37f153a833504d1f2f2fc6642da6398d6446d0 100644 --- a/packages/resolver/src/resolve-methods.js +++ b/packages/resolver/src/resolve-methods.js @@ -6,15 +6,12 @@ const { JsonqlResolverAppError, JsonqlValidationError, JsonqlAuthorisationError, - getErrorNameByInstanceWithDefault, + getErrorNameByInstance, UNKNOWN_ERROR } = require('jsonql-errors') // @TODO this should move to the jsonql-utils! const { provideUserdata } = require('jsonql-jwt') -const { - MODULE_TYPE, - DEFAULT_RESOLVER_IMPORT_FILE_NAME -} = require('jsonql-constants') +const { MODULE_TYPE } = require('jsonql-constants') const { handleOutput, ctxErrorHandler, @@ -22,26 +19,12 @@ const { extractArgsFromPayload } = require('jsonql-utils') const { getDebug } = require('./utils') -const { searchResolvers } = require('./search-resolvers') +const { searchResolvers, importFromModule } = require('./search-resolvers') const { validateAndCall } = require('./validate-and-call') const { provideNodeClients } = require('./provide-node-clients') const debug = getDebug('resolve-method') -/** - * @TODO we might have to change to the contract folder instead - * New for ES6 module features - * @param {string} resolverDir resolver directory - * @param {string} type of resolver - * @param {string} resolverName name of resolver - * @return {function} the imported resolver - */ -function importFromModule(resolverDir, type, resolverName) { - debug('[importFromModule]', resolverDir, type, resolverName) - const resolvers = require( join(resolverDir, DEFAULT_RESOLVER_IMPORT_FILE_NAME) ) - return resolvers[ [type, resolverName].join('') ] -} - /** * A new method breaking out the get resolver and prepare code * then make the original resolveMethod as a koa render handler @@ -53,7 +36,7 @@ function importFromModule(resolverDir, type, resolverName) { * @param {*} [userdata=false] if there is any * @return {*} result - process result from resolver */ -const executeResolver = async (opts, type, resolverName, payload, contract, userdata = false) => { +const executeResolver = (opts, type, resolverName, payload, contract, userdata = false) => { let fn; const { sourceType } = contract; if (sourceType === MODULE_TYPE) { @@ -64,18 +47,21 @@ const executeResolver = async (opts, type, resolverName, payload, contract, user } const args = extractArgsFromPayload(payload, type) // inject the node client if any - fn = await provideNodeClients(fn, opts) - // here we could apply the userdata to the method - const result = await validateAndCall( - provideUserdata(fn, userdata), // always call this one even auth is false - args, - contract, - type, - resolverName, - opts) - // @TODO if we need to check returns in the future - debug('called and now serve up', result) - return result; + // @0.9.0 change everything to promise and stop using async + return provideNodeClients(fn, opts) + .then(fn => validateAndCall( + provideUserdata(fn, userdata), // always call this one even auth is false + args, + contract, + type, + resolverName, + opts) + ) + .then(result => { + // @TODO if we need to check returns in the future + debug('called and now serve up', result) + return result + }) } /** @@ -88,7 +74,7 @@ const executeResolver = async (opts, type, resolverName, payload, contract, user * @param {object} contract to search via the file name info * @return {mixed} depends on the contract */ -const resolverRenderHandler = async (ctx, type, opts, contract) => { +async function resolverRenderHandler(ctx, type, opts, contract) { const { payload, resolverName, userdata } = ctx.state.jsonql; debug('resolveMethod', resolverName, payload, type) // There must be only one method call @@ -96,15 +82,20 @@ const resolverRenderHandler = async (ctx, type, opts, contract) => { // first try to catch the resolve error try { const result = await executeResolver(opts, type, resolverName, payload, contract, userdata) + return renderHandler(ctx, packResult(result)) } catch (e) { debug('resolveMethod error', e) - let errorName = getErrorNameByInstanceWithDefault([ + let errorName = getErrorNameByInstance([ JsonqlResolverNotFoundError, JsonqlAuthorisationError, JsonqlValidationError, JsonqlResolverAppError ], e) + // if this is an unknown error then it will be JsonqlResolverAppError + if (errorName === UNKNOWN_ERROR) { + errorName = 'JsonqlResolverAppError' + } return ctxErrorHandler(ctx, errorName, e) } diff --git a/packages/resolver/src/search-resolvers.js b/packages/resolver/src/search-resolvers.js index 3ea03c0206ffc90e21f2937e8479e58289fb95ce..38521f6a12e6cf021c47c41ce26d1c5e5fe26200 100644 --- a/packages/resolver/src/search-resolvers.js +++ b/packages/resolver/src/search-resolvers.js @@ -3,12 +3,27 @@ const { JsonqlResolverNotFoundError } = require('jsonql-errors') const { getPathToFn, findFromContract } = require('jsonql-utils') +const { DEFAULT_RESOLVER_IMPORT_FILE_NAME } = require('jsonql-constants') const { getDebug } = require('./utils') const debug = getDebug('search-resolvers') const prod = process.env.NODE_ENV === 'production'; +/** + * @TODO we might have to change to the contract folder instead + * New for ES6 module features + * @param {string} resolverDir resolver directory + * @param {string} type of resolver + * @param {string} resolverName name of resolver + * @return {function} the imported resolver + */ +function importFromModule(resolverDir, type, resolverName) { + debug('[importFromModule]', resolverDir, type, resolverName) + const resolvers = require( join(resolverDir, DEFAULT_RESOLVER_IMPORT_FILE_NAME) ) + return resolvers[ [type, resolverName].join('') ] +} + /** * search for the file starting with * 1. Is the path in the contract (or do we have a contract file) @@ -42,4 +57,7 @@ function searchResolvers(name, type, opts, contract) { } } -module.exports = { searchResolvers } +module.exports = { + searchResolvers, + importFromModule +} diff --git a/packages/resolver/src/utils.js b/packages/resolver/src/utils.js index b92db0c5485dc983cfbe6cc0eb63c949411fde79..ffa36f8995a5a396f3dcf2168090b0ba6c2e452c 100644 --- a/packages/resolver/src/utils.js +++ b/packages/resolver/src/utils.js @@ -2,8 +2,20 @@ const debug = require('debug') const MODULE_NAME = 'jsonql-resolver' +/** + * return the debug method + * @param {string} name the base name + * @return {function} the debug function + */ +const getDebug = function(name) { + return debug(MODULE_NAME).extend(name) +} + +// The following are moved back from utils + + + + module.exports = { - getDebug: function(name) { - return debug(MODULE_NAME).extend(name) - } + getDebug } diff --git a/packages/resolver/src/validate-and-call.js b/packages/resolver/src/validate-and-call.js index 16c7996ce07d3856f8eef9cf969b137b00790887..d1b4729b7ba37ba4a7a49dc71812966e48da7d09 100644 --- a/packages/resolver/src/validate-and-call.js +++ b/packages/resolver/src/validate-and-call.js @@ -61,13 +61,8 @@ function validateAndCall(fn, args, contract, type, name, opts) { } return Promise - .resolve(Reflect.apply(fn, null, args)) - .then(applyJwtMethod(type, name, opts, contract)) - /* we will debug this from the other side - .catch(e => { - debug(`error throw inside the promise`, e) - throw e; - }) */ + .resolve(Reflect.apply(fn, null, args)) + .then(applyJwtMethod(type, name, opts, contract)) } module.exports = { validateAndCall } diff --git a/packages/resolver/tests/fixtures/contract/contract.json b/packages/resolver/tests/fixtures/contract/contract.json index ccb1840ad3c6d5afb77d67afe14a2f521c420a9f..bd9eef966d9c9ec3ac2700669c73ec40b5a5adcf 100644 --- a/packages/resolver/tests/fixtures/contract/contract.json +++ b/packages/resolver/tests/fixtures/contract/contract.json @@ -61,10 +61,23 @@ "description": "name with prefix" } ] + }, + "toFail": { + "file": "/home/joel/projects/open-source/jsonql/packages/resolver/tests/fixtures/resolvers/query/to-fail.js", + "description": "This resolver will fail and throw an error", + "params": [], + "returns": [ + { + "type": [ + "any" + ], + "description": "a variable that didn't exist" + } + ] } }, "mutation": {}, "auth": {}, - "timestamp": 1566054454129, + "timestamp": 1570202373, "sourceType": "script" } diff --git a/packages/resolver/tests/fixtures/resolvers/query/to-fail.js b/packages/resolver/tests/fixtures/resolvers/query/to-fail.js index ad66613efc4034cc1664a2900a8266f295496cdc..2a681edb106d57177c5cfd53c57c46a00d5fed16 100644 --- a/packages/resolver/tests/fixtures/resolvers/query/to-fail.js +++ b/packages/resolver/tests/fixtures/resolvers/query/to-fail.js @@ -1,11 +1,7 @@ /** * This resolver will fail and throw an error - * @return a variable that didn't exist + * @return {*} a variable that didn't exist */ module.exports = function toFail() { - try { - return 1; - } catch(e) { - throw new Error(e) - } + return varNotExisted; } diff --git a/packages/resolver/tests/throw.test.js b/packages/resolver/tests/throw.test.js index 9d4bf285130da1d77d7e587faae3bf0477fce25a..b485899464e46cdc3202d0c348e69d8d60325b30 100644 --- a/packages/resolver/tests/throw.test.js +++ b/packages/resolver/tests/throw.test.js @@ -1,6 +1,13 @@ // testing the throw error problem const test = require('ava') -const { JsonqlResolverAppError } = require('jsonql-errors') +const { + JsonqlResolverNotFoundError, + JsonqlAuthorisationError, + JsonqlValidationError, + JsonqlResolverAppError, + getErrorNameByInstance, + UNKNOWN_ERROR +} = require('jsonql-errors') const { join } = require('path') const { executeResolver } = require('../') @@ -18,11 +25,30 @@ test.before(async t => { }) -test(`When resolver throw error, it should able to throw back a JsonqlResolverAppError`, async t => { - const res = {} - - t.false(!!res.params) - - t.false(Array.isArray(res.params)) +test.cb(`When resolver throw error, it should able to throw back a JsonqlResolverAppError`, t => { + t.plan(1) + + const resolverName = 'toFail'; + const payload = createQuery(resolverName, []) + + executeResolver( + t.context.opts, + 'query', + resolverName, + payload[resolverName], + t.context.contract + ) + .catch(e => { + let errorName = getErrorNameByInstance([ + JsonqlResolverNotFoundError, + JsonqlAuthorisationError, + JsonqlValidationError, + JsonqlResolverAppError + ], e) + + t.is(errorName, UNKNOWN_ERROR) + t.end() + + }) })