Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pluggable executor #2574

Merged
merged 5 commits into from
Apr 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 2 additions & 7 deletions packages/apollo-engine-reporting/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import {
ExecutionArgs,
GraphQLError,
} from 'graphql';
import {
GraphQLExtension,
GraphQLResponse,
EndHandler,
} from 'graphql-extensions';
import { GraphQLExtension, EndHandler } from 'graphql-extensions';
import { Trace, google } from 'apollo-engine-reporting-protobuf';

import { EngineReportingOptions, GenerateClientInfo } from './agent';
Expand Down Expand Up @@ -279,8 +275,7 @@ export class EngineReportingExtension<TContext = any>
};
}

public willSendResponse(o: { graphqlResponse: GraphQLResponse }) {
const { errors } = o.graphqlResponse;
public didEncounterErrors(errors: GraphQLError[]) {
if (errors) {
errors.forEach((error: GraphQLError) => {
// By default, put errors on the root node.
Expand Down
13 changes: 0 additions & 13 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ import {
PluginDefinition,
} from './types';

import { FormatErrorExtension } from './formatters';

import { gql } from './index';

import {
Expand Down Expand Up @@ -331,17 +329,6 @@ export class ApolloServerBase {
// or cacheControl.
this.extensions = [];

const debugDefault =
process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
const debug =
requestOptions.debug !== undefined ? requestOptions.debug : debugDefault;

// Error formatting should happen after the engine reporting agent, so that
// engine gets the unmasked errors if necessary
this.extensions.push(
() => new FormatErrorExtension(requestOptions.formatError, debug),
);

// In an effort to avoid over-exposing the API key itself, extract the
// service ID from the API key for plugins which only needs service ID.
// The truthyness of this value can also be used in other forks of logic
Expand Down
35 changes: 0 additions & 35 deletions packages/apollo-server-core/src/formatters.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/apollo-server-core/src/graphqlOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DataSource } from 'apollo-datasource';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
import { GraphQLParseOptions } from 'graphql-tools';
import { ValueOrPromise } from 'apollo-server-env';
import { GraphQLExecutor } from '../dist/requestPipelineAPI';

/*
* GraphQLServerOptions
Expand All @@ -38,6 +39,7 @@ export interface GraphQLServerOptions<
rootValue?: ((parsedQuery: DocumentNode) => TRootValue) | TRootValue;
context?: TContext | (() => never);
validationRules?: Array<(context: ValidationContext) => any>;
executor?: GraphQLExecutor;
formatResponse?: Function;
fieldResolver?: GraphQLFieldResolver<any, TContext>;
debug?: boolean;
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-server-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export {
// ApolloServer Base class
export { ApolloServerBase } from './ApolloServer';
export * from './types';
export * from './requestPipelineAPI';

// This currently provides the ability to have syntax highlighting as well as
// consistency between client and server gql tags
Expand Down
68 changes: 49 additions & 19 deletions packages/apollo-server-core/src/requestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
DocumentNode,
getOperationAST,
ExecutionArgs,
ExecutionResult,
GraphQLError,
GraphQLFormattedError,
} from 'graphql';
Expand All @@ -29,13 +28,16 @@ import {
ValidationError,
PersistedQueryNotSupportedError,
PersistedQueryNotFoundError,
formatApolloErrors,
} from 'apollo-server-errors';
import {
GraphQLRequest,
GraphQLResponse,
GraphQLRequestContext,
InvalidGraphQLRequestError,
ValidationRule,
GraphQLExecutor,
GraphQLExecutionResult,
} from '../dist/requestPipelineAPI';
import {
ApolloServerPlugin,
Expand Down Expand Up @@ -73,6 +75,7 @@ export interface GraphQLRequestPipelineConfig<TContext> {

rootValue?: ((document: DocumentNode) => any) | any;
validationRules?: ValidationRule[];
executor?: GraphQLExecutor;
fieldResolver?: GraphQLFieldResolver<any, TContext>;

dataSources?: () => DataSources<TContext>;
Expand Down Expand Up @@ -316,11 +319,20 @@ export async function processGraphQLRequest<TContext>(
);

try {
response = (await execute(
requestContext.document,
request.operationName,
request.variables,
)) as GraphQLResponse;
const result = await execute(requestContext as WithRequired<
typeof requestContext,
'document' | 'operation' | 'operationName'
>);

if (result.errors) {
extensionStack.didEncounterErrors(result.errors);
}

response = {
...result,
errors: result.errors ? formatErrors(result.errors) : undefined,
};

executionDidEnd();
} catch (executionError) {
executionDidEnd(executionError);
Expand Down Expand Up @@ -395,10 +407,13 @@ export async function processGraphQLRequest<TContext>(
}

async function execute(
document: DocumentNode,
operationName: GraphQLRequest['operationName'],
variables: GraphQLRequest['variables'],
): Promise<ExecutionResult> {
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'document' | 'operationName' | 'operation'
>,
): Promise<GraphQLExecutionResult> {
const { request, document } = requestContext;

const executionArgs: ExecutionArgs = {
schema: config.schema,
document,
Expand All @@ -407,8 +422,8 @@ export async function processGraphQLRequest<TContext>(
? config.rootValue(document)
: config.rootValue,
contextValue: requestContext.context,
variableValues: variables,
operationName,
variableValues: request.variables,
operationName: request.operationName,
fieldResolver: config.fieldResolver,
};

Expand All @@ -417,7 +432,11 @@ export async function processGraphQLRequest<TContext>(
});

try {
return await graphql.execute(executionArgs);
if (config.executor) {
return await config.executor(requestContext);
} else {
return await graphql.execute(executionArgs);
}
} finally {
executionDidEnd();
}
Expand Down Expand Up @@ -454,17 +473,28 @@ export async function processGraphQLRequest<TContext>(
: [errorOrErrors];

return sendResponse({
errors: errors.map(err =>
fromGraphQLError(
err,
errorClass && {
errorClass,
},
errors: formatErrors(
errors.map(err =>
fromGraphQLError(
err,
errorClass && {
errorClass,
},
),
),
),
});
}

function formatErrors(
errors: ReadonlyArray<GraphQLError>,
): ReadonlyArray<GraphQLFormattedError> {
return formatApolloErrors(errors, {
formatter: config.formatError,
debug: requestContext.debug,
});
}

function initializeRequestListenerDispatcher(): Dispatcher<
GraphQLRequestListener
> {
Expand Down
29 changes: 25 additions & 4 deletions packages/apollo-server-core/src/requestPipelineAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
// circular dependency issues from the `apollo-server-plugin-base` package
// depending on the types in it.

import { Request, Response } from 'apollo-server-env';
import {
Request,
Response,
ValueOrPromise,
WithRequired,
} from 'apollo-server-env';
import {
GraphQLSchema,
ValidationContext,
ASTVisitor,
GraphQLError,
GraphQLFormattedError,
OperationDefinitionNode,
DocumentNode,
GraphQLError,
} from 'graphql';
import { KeyValueCache } from 'apollo-server-caching';

Expand All @@ -27,14 +33,16 @@ export interface GraphQLServiceContext {
export interface GraphQLRequest {
query?: string;
operationName?: string;
variables?: { [name: string]: any };
variables?: VariableValues;
extensions?: Record<string, any>;
http?: Pick<Request, 'url' | 'method' | 'headers'>;
}

export type VariableValues = { [name: string]: any };

export interface GraphQLResponse {
data?: Record<string, any>;
errors?: GraphQLError[];
errors?: ReadonlyArray<GraphQLFormattedError>;
extensions?: Record<string, any>;
http?: Pick<Response, 'headers'>;
}
Expand Down Expand Up @@ -72,3 +80,16 @@ export interface GraphQLRequestContext<TContext = Record<string, any>> {
export type ValidationRule = (context: ValidationContext) => ASTVisitor;

export class InvalidGraphQLRequestError extends Error {}

export type GraphQLExecutor<TContext = Record<string, any>> = (
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'document' | 'operationName' | 'operation'
>,
) => ValueOrPromise<GraphQLExecutionResult>;

export type GraphQLExecutionResult = {
data?: Record<string, any>;
errors?: ReadonlyArray<GraphQLError>;
extensions?: Record<string, any>;
};
1 change: 1 addition & 0 deletions packages/apollo-server-core/src/runHttpQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export async function runHttpQuery(
rootValue: options.rootValue,
context: options.context || {},
validationRules: options.validationRules,
executor: options.executor,
fieldResolver: options.fieldResolver,

// FIXME: Use proper option types to ensure this
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-server-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type BaseConfig = Pick<
| 'debug'
| 'rootValue'
| 'validationRules'
| 'executor'
| 'formatResponse'
| 'fieldResolver'
| 'tracing'
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-errors/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export class UserInputError extends ApolloError {
}

export function formatApolloErrors(
errors: Array<Error>,
errors: ReadonlyArray<Error>,
options?: {
formatter?: (error: GraphQLError) => GraphQLFormattedError;
debug?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,9 +625,9 @@ export function testApolloServer<AS extends ApolloServerBase>(
context: TContext;
}) {
expect(o.graphqlResponse.errors.length).toEqual(1);
// formatError should be called after extensions
expect(formatError).not.toBeCalled();
// validationRules should be called before extensions
// formatError should be called before willSendResponse
expect(formatError).toHaveBeenCalledTimes(1);
// validationRule should be called before willSendResponse
expect(validationRule).toHaveBeenCalledTimes(1);
willSendResponseInExtension();
}
Expand Down
10 changes: 9 additions & 1 deletion packages/graphql-extensions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
DocumentNode,
ResponsePath,
FieldNode,
GraphQLError,
} from 'graphql';

import { Request } from 'apollo-server-env';
export { Request } from 'apollo-server-env';

import {
GraphQLResponse,
Expand Down Expand Up @@ -52,6 +52,7 @@ export class GraphQLExtension<TContext = any> {
public didResolveOperation?(o: {
requestContext: GraphQLRequestContext<TContext>;
}): void;
public didEncounterErrors?(errors: ReadonlyArray<GraphQLError>): void;

public willSendResponse?(o: {
graphqlResponse: GraphQLResponse;
Expand Down Expand Up @@ -121,6 +122,13 @@ export class GraphQLExtensionStack<TContext = any> {
}
});
}
public didEncounterErrors(errors: ReadonlyArray<GraphQLError>) {
this.extensions.forEach(extension => {
if (extension.didEncounterErrors) {
extension.didEncounterErrors(errors);
}
});
}

public willSendResponse(o: {
graphqlResponse: GraphQLResponse;
Expand Down