Example #1
0
export function request(...args) {
  let executionResult;
  let [
    schema,
    source,
    rootValue,
    contextValue,
    variableValues,
    operationName,
    fieldResolver,
    subscriptionFieldResolver,
    extensions,
    extensionData,
  ] = args;

  if (args.length === 1) {
    if (!isObject(schema) || Array.isArray(schema)) {
      return Promise.resolve({
        errors: [
          new GraphQLError(
            'first argument must be GraphQLSchema ' + 'or an arguments object',
          ),
        ],
      });
    }

    // standard
    source = schema.source;
    rootValue = schema.rootValue;
    contextValue = schema.contextValue;
    variableValues = schema.variableValues;
    operationName = schema.operationName;
    fieldResolver = schema.fieldResolver;
    subscriptionFieldResolver = schema.subscriptionFieldResolver;
    extensions = schema.extensions;
    extensionData = schema.extensionData;

    // set schema to the actual schema value, this must always go last
    // because it will overwrite the execution arguments object variable
    schema = schema.schema;
  }

  const malformedError = new GraphQLError(
    'malformed response, ' +
      'verify middleware returns correclty structured ' +
      'response data',
  );
  const extensionMap = new FactoryExtensionMap(extensions, extensionData);
  extensionMap.requestStarted();
  extensionMap.parsingStarted();

  const {
    errors,
    runtimeSchema,
    document,
    before,
    after,
  } = applyDirectiveVisitors(schema, source, variableValues, extensionMap);
  extensionMap.parsingEnded();

  if (errors) {
    extensionMap.requestEnded();
    return !extensions ? { errors } : { errors, extensions: extensionMap.data };
  }

  extensionMap.validationStarted();
  const schemaValidationErrors = validateSchema(runtimeSchema);
  if (schemaValidationErrors.length > 0) {
    extensionMap.validationEnded();
    extensionMap.requestEnded();
    return Promise.resolve(
      !extensions
        ? { errors: schemaValidationErrors }
        : { errors: schemaValidationErrors, extensions: extensionMap.data },
    );
  }
  const validationErrors = validate(runtimeSchema, document);
  if (validationErrors.length > 0) {
    extensionMap.validationEnded();
    extensionMap.requestEnded();
    return Promise.resolve(
      !extensions
        ? { errors: validationErrors }
        : { errors: validationErrors, extensions: extensionMap.data },
    );
  }
  extensionMap.validationEnded();
  extensionMap.executionStarted();
  const operation = document.definitions[0].operation;
  const execution =
    operation === 'subscription'
      ? () => {
          return subscribe(
            runtimeSchema,
            document,
            rootValue,
            contextValue,
            variableValues,
            operationName,
            fieldResolver,
            subscriptionFieldResolver,
          );
        }
      : () => {
          return execute(
            runtimeSchema,
            document,
            rootValue,
            contextValue,
            variableValues,
            operationName,
            fieldResolver,
          );
        };

  const resolveQueue = before
    .concat({
      level: 'execution',
      type: operation,
      class: 'execution',
      name: execution.name || operation,
      resolve: execution,
      args: {},
    })
    .concat(after);

  // check for pending build tasks in the definition
  // if there are, set the initial value to resolve
  // them first, otherwise use the rootValue
  const build = _.get(runtimeSchema, 'definition._build');
  const initialValue = isPromise(build)
    ? build.then(() => rootValue)
    : rootValue;

  const fragments = document.definitions.reduce((frags, definition) => {
    if (definition.kind === Kind.FRAGMENT_DEFINITION) {
      frags[definition.name.value] = definition;
    }
    return frags;
  }, {});

  let detailedMap;
  const result = promiseReduce(
    resolveQueue,
    (src, current) => {
      if (detailedMap) {
        extensionMap.resolverEnded(detailedMap);
      }
      detailedMap = extensionMap.resolverStarted(undefined, current);

      if (current.type === 'after' && isWellFormedResponse(src)) {
        executionResult = source;
      }

      try {
        return current.resolve(
          executionResult || src,
          current.args,
          contextValue,
          {
            fieldName: undefined,
            fieldNodes: [],
            returnType: undefined,
            parentType: undefined,
            schema: runtimeSchema,
            fragments,
            rootValue,
            operation: document.definitions[0],
            variableValues,
          },
        );
      } catch (err) {
        return Promise.reject(err);
      }
    },
    initialValue,
  );

  return Promise.resolve(result)
    .then(finalResult => {
      extensionMap.resolverEnded(detailedMap);
      extensionMap.executionEnded();
      extensionMap.requestEnded();

      if (isWellFormedResponse(finalResult)) {
        return addExtensions(finalResult, extensionMap, extensions);
      }

      const correctedResult = {
        data: hasOwn(finalResult, 'data')
          ? finalResult.data
          : hasOwn(executionResult, 'data') ? executionResult.data : null,
        errors: isArray(finalResult, 'errors')
          ? finalResult.errors
          : isArray(executionResult, 'errors') ? executionResult.errors : [],
      };
      correctedResult.errors.push(malformedError);
      return addExtensions(correctedResult, extensionMap, extensions);
    })
    .catch(err => {
      extensionMap.resolverEnded(detailedMap);
      extensionMap.executionEnded();
      extensionMap.requestEnded();

      if (isWellFormedResponse(executionResult)) {
        executionResult.errors = isArray(executionResult, 'errors')
          ? executionResult.errors
          : [];
        executionResult.errors.push(err);
        return Promise.resolve(executionResult);
      }
      return Promise.resolve({
        errors: [err, malformedError],
      });
    });
}
Example #2
0
      .then(optionsData => {
        // Assert that schema is required.
        if (!optionsData.schema) {
          throw new Error('GraphQL middleware options must contain a schema.');
        }

        // Collect information from the options data object.
        const schema = optionsData.schema;
        const rootValue = optionsData.rootValue;
        const fieldResolver = optionsData.fieldResolver;
        const graphiql = optionsData.graphiql;

        context = optionsData.context || request;

        let validationRules = specifiedRules;
        if (optionsData.validationRules) {
          validationRules = validationRules.concat(optionsData.validationRules);
        }

        // GraphQL HTTP only supports GET and POST methods.
        if (request.method !== 'GET' && request.method !== 'POST') {
          response.setHeader('Allow', 'GET, POST');
          throw httpError(405, 'GraphQL only supports GET and POST requests.');
        }

        // Get GraphQL params from the request and POST body data.
        query = params.query;
        variables = params.variables;
        operationName = params.operationName;
        showGraphiQL = graphiql && canDisplayGraphiQL(request, params);

        // If there is no query, but GraphiQL will be displayed, do not produce
        // a result, otherwise return a 400: Bad Request.
        if (!query) {
          if (showGraphiQL) {
            return null;
          }
          throw httpError(400, 'Must provide query string.');
        }

        // Validate Schema
        const schemaValidationErrors = validateSchema(schema);
        if (schemaValidationErrors.length > 0) {
          // Return 500: Internal Server Error if invalid schema.
          response.statusCode = 500;
          return { errors: schemaValidationErrors };
        }

        //  GraphQL source.
        const source = new Source(query, 'GraphQL request');

        // Parse source to AST, reporting any syntax error.
        try {
          documentAST = parse(source);
        } catch (syntaxError) {
          // Return 400: Bad Request if any syntax errors errors exist.
          response.statusCode = 400;
          return { errors: [syntaxError] };
        }

        // Validate AST, reporting any errors.
        const validationErrors = validate(schema, documentAST, validationRules);
        if (validationErrors.length > 0) {
          // Return 400: Bad Request if any validation errors exist.
          response.statusCode = 400;
          return { errors: validationErrors };
        }

        // Only query operations are allowed on GET requests.
        if (request.method === 'GET') {
          // Determine if this GET request will perform a non-query.
          const operationAST = getOperationAST(documentAST, operationName);
          if (operationAST && operationAST.operation !== 'query') {
            // If GraphiQL can be shown, do not perform this query, but
            // provide it to GraphiQL so that the requester may perform it
            // themselves if desired.
            if (showGraphiQL) {
              return null;
            }

            // Otherwise, report a 405: Method Not Allowed error.
            response.setHeader('Allow', 'POST');
            throw httpError(
              405,
              `Can only perform a ${operationAST.operation} operation ` +
                'from a POST request.',
            );
          }
        }
        // Perform the execution, reporting any errors creating the context.
        try {
          return execute(
            schema,
            documentAST,
            rootValue,
            context,
            variables,
            operationName,
            fieldResolver,
          );
        } catch (contextError) {
          // Return 400: Bad Request if any execution context errors exist.
          response.statusCode = 400;
          return { errors: [contextError] };
        }
      })