Pierre Noël 4b7fbadef5
fix shadow crud breaking build when resolvers are not defined in a plugin (#5899)
* fix shadow crud breaking build when resolvers are not defined in a plugin

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

* refacto

Signed-off-by: Pierre Noël <pierre.noel@strapi.io>

Co-authored-by: Pierre Noël <pierre.noel@strapi.io>
2020-04-22 15:41:40 +02:00

196 lines
4.7 KiB
JavaScript

/**
* Build queries and mutation resolvers
*/
'use strict';
const _ = require('lodash');
const compose = require('koa-compose');
const { policy: policyUtils } = require('strapi-utils');
const {
convertToParams,
convertToQuery,
amountLimiting,
getAction,
getActionDetails,
isResolvablePath,
} = require('./utils');
const buildMutation = (mutationName, config) => {
const { resolver, resolverOf, transformOutput = _.identity } = config;
if (_.isFunction(resolver) && !isResolvablePath(resolverOf)) {
throw new Error(
`Cannot create mutation "${mutationName}". Missing "resolverOf" option with custom resolver.`
);
}
const policiesMiddleware = compose(getPolicies(config));
// custom resolvers
if (_.isFunction(resolver)) {
return async (root, options = {}, graphqlContext) => {
const ctx = buildMutationContext({ options, graphqlContext });
await policiesMiddleware(ctx);
graphqlContext.context = ctx;
return resolver(root, options, graphqlContext);
};
}
const action = getAction(resolver);
return async (root, options = {}, graphqlContext) => {
const ctx = buildMutationContext({ options, graphqlContext });
await policiesMiddleware(ctx);
const values = await action(ctx);
const result = ctx.body || values;
if (_.isError(result)) {
throw result;
}
return transformOutput(result);
};
};
const buildMutationContext = ({ options, graphqlContext }) => {
const { context } = graphqlContext;
const ctx = context.app.createContext(_.clone(context.req), _.clone(context.res));
if (options.input && options.input.where) {
ctx.params = convertToParams(options.input.where || {});
} else {
ctx.params = {};
}
if (options.input && options.input.data) {
ctx.request.body = options.input.data || {};
} else {
ctx.request.body = options;
}
return ctx;
};
const buildQuery = (queryName, config) => {
const { resolver } = config;
try {
validateResolverOption(config);
} catch (error) {
throw new Error(`Cannot create query "${queryName}": ${error.message}`);
}
const policiesMiddleware = compose(getPolicies(config));
// custom resolvers
if (_.isFunction(resolver)) {
return async (root, options = {}, graphqlContext) => {
const { ctx, opts } = buildQueryContext({ options, graphqlContext });
await policiesMiddleware(ctx);
graphqlContext.context = ctx;
return resolver(root, opts, graphqlContext);
};
}
const action = getAction(resolver);
return async (root, options = {}, graphqlContext) => {
const { ctx } = buildQueryContext({ options, graphqlContext });
// duplicate context
await policiesMiddleware(ctx);
const values = await action(ctx);
const result = ctx.body || values;
if (_.isError(result)) {
throw result;
}
return result;
};
};
const validateResolverOption = config => {
const { resolver, resolverOf, policies } = config;
if (_.isFunction(resolver) && !isResolvablePath(resolverOf)) {
throw new Error(`Missing "resolverOf" option with custom resolver.`);
}
if (!_.isUndefined(policies) && (!Array.isArray(policies) || !_.every(policies, _.isString))) {
throw new Error('Policies option must be an array of string.');
}
return true;
};
const buildQueryContext = ({ options, graphqlContext }) => {
const { context } = graphqlContext;
const _options = _.cloneDeep(options);
const ctx = context.app.createContext(_.clone(context.req), _.clone(context.res));
// Note: we've to used the Object.defineProperties to reset the prototype. It seems that the cloning the context
// cause a lost of the Object prototype.
const opts = amountLimiting(_options);
ctx.query = {
...convertToParams(_.omit(opts, 'where')),
...convertToQuery(opts.where),
};
ctx.params = convertToParams(opts);
return { ctx, opts };
};
/**
* Checks if a resolverPath (resolver or resovlerOf) might be resolved
*/
const getPolicies = config => {
const { resolver, policies = [], resolverOf } = config;
const { api, plugin } = config['_metadatas'] || {};
const policyFns = [];
const { controller, action, plugin: pathPlugin } = isResolvablePath(resolverOf)
? getActionDetails(resolverOf)
: getActionDetails(resolver);
const globalPolicy = policyUtils.globalPolicy({
controller,
action,
plugin: pathPlugin,
});
policyFns.push(globalPolicy);
if (strapi.plugins['users-permissions']) {
policies.unshift('plugins::users-permissions.permissions');
}
policies.forEach(policy => {
const policyFn = policyUtils.get(policy, plugin, api);
policyFns.push(policyFn);
});
return policyFns;
};
module.exports = {
buildQuery,
buildMutation,
};