mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 11:54:10 +00:00 
			
		
		
		
	* Fix #10125 - accept falsy values returned by controllers * Add test for falsy ctx.body on built graphql resolver * Use test instead of it in unit tests
		
			
				
	
	
		
			205 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			4.8 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, isShadowCrud = false } = 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, info) => {
 | 
						|
      const ctx = buildMutationContext({ options, graphqlContext, isShadowCrud });
 | 
						|
 | 
						|
      await policiesMiddleware(ctx);
 | 
						|
      graphqlContext.context = ctx;
 | 
						|
 | 
						|
      return resolver(root, options, graphqlContext, info);
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  const action = getAction(resolver);
 | 
						|
 | 
						|
  return async (root, options = {}, graphqlContext) => {
 | 
						|
    const ctx = buildMutationContext({ options, graphqlContext, isShadowCrud });
 | 
						|
 | 
						|
    await policiesMiddleware(ctx);
 | 
						|
 | 
						|
    const values = await action(ctx);
 | 
						|
    const result = ctx.body !== undefined ? ctx.body : values;
 | 
						|
 | 
						|
    if (_.isError(result)) {
 | 
						|
      throw result;
 | 
						|
    }
 | 
						|
 | 
						|
    return transformOutput(result);
 | 
						|
  };
 | 
						|
};
 | 
						|
 | 
						|
const buildMutationContext = ({ options, graphqlContext, isShadowCrud }) => {
 | 
						|
  const { context } = graphqlContext;
 | 
						|
 | 
						|
  const ctx = cloneKoaContext(context);
 | 
						|
 | 
						|
  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;
 | 
						|
  }
 | 
						|
 | 
						|
  if (isShadowCrud) {
 | 
						|
    ctx.query = convertToParams(_.omit(options, 'input'));
 | 
						|
  }
 | 
						|
 | 
						|
  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, info) => {
 | 
						|
      const { ctx, opts } = buildQueryContext({ options, graphqlContext });
 | 
						|
 | 
						|
      await policiesMiddleware(ctx);
 | 
						|
      graphqlContext.context = ctx;
 | 
						|
 | 
						|
      return resolver(root, opts, graphqlContext, info);
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  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 !== undefined ? 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 cloneKoaContext = ctx => {
 | 
						|
  return Object.assign(ctx.app.createContext(_.clone(ctx.req), _.clone(ctx.res)), {
 | 
						|
    state: {
 | 
						|
      ...ctx.state,
 | 
						|
    },
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
const buildQueryContext = ({ options, graphqlContext }) => {
 | 
						|
  const { context } = graphqlContext;
 | 
						|
  const _options = _.cloneDeep(options);
 | 
						|
 | 
						|
  const ctx = cloneKoaContext(context);
 | 
						|
 | 
						|
  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,
 | 
						|
};
 |