mirror of
https://github.com/vale981/apollo-server
synced 2025-03-06 02:01:40 -05:00
Add didResolveOperation
hook to request pipeline API
This commit is contained in:
parent
f942af77b2
commit
3fab039c1c
4 changed files with 54 additions and 46 deletions
|
@ -3,7 +3,6 @@ import {
|
|||
GraphQLFieldResolver,
|
||||
specifiedRules,
|
||||
DocumentNode,
|
||||
OperationDefinitionNode,
|
||||
getOperationAST,
|
||||
ExecutionArgs,
|
||||
ExecutionResult,
|
||||
|
@ -76,15 +75,14 @@ export type DataSources<TContext> = {
|
|||
[name: string]: DataSource<TContext>;
|
||||
};
|
||||
|
||||
export interface GraphQLRequestPipeline<TContext> {
|
||||
willExecuteOperation?(operation: OperationDefinitionNode): void;
|
||||
}
|
||||
|
||||
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
|
||||
export class GraphQLRequestPipeline<TContext> {
|
||||
plugins: ApolloServerPlugin[];
|
||||
|
||||
constructor(private config: GraphQLRequestPipelineConfig<TContext>) {
|
||||
enableGraphQLExtensions(config.schema);
|
||||
this.plugins = config.plugins || [];
|
||||
}
|
||||
|
||||
async processRequest(
|
||||
|
@ -93,13 +91,11 @@ export class GraphQLRequestPipeline<TContext> {
|
|||
const config = this.config;
|
||||
|
||||
const requestListeners: GraphQLRequestListener<TContext>[] = [];
|
||||
if (config.plugins) {
|
||||
for (const plugin of config.plugins) {
|
||||
if (!plugin.requestDidStart) continue;
|
||||
const listener = plugin.requestDidStart(requestContext);
|
||||
if (listener) {
|
||||
requestListeners.push(listener);
|
||||
}
|
||||
for (const plugin of this.plugins) {
|
||||
if (!plugin.requestDidStart) continue;
|
||||
const listener = plugin.requestDidStart(requestContext);
|
||||
if (listener) {
|
||||
requestListeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,8 +106,6 @@ export class GraphQLRequestPipeline<TContext> {
|
|||
|
||||
this.initializeDataSources(requestContext);
|
||||
|
||||
await dispatcher.invokeAsync('prepareRequest', requestContext);
|
||||
|
||||
const request = requestContext.request;
|
||||
|
||||
let { query, extensions } = request;
|
||||
|
@ -225,22 +219,25 @@ export class GraphQLRequestPipeline<TContext> {
|
|||
|
||||
validationDidEnd();
|
||||
|
||||
// FIXME: If we want to guarantee an operation has been set when invoking
|
||||
// `willExecuteOperation` and executionDidStart`, we need to throw an
|
||||
// error here and not leave this to `buildExecutionContext` in
|
||||
// `graphql-js`.
|
||||
const operation = getOperationAST(document, request.operationName);
|
||||
|
||||
// If we don't find an operation, we'll leave it to `buildExecutionContext`
|
||||
// in `graphql-js` to throw an appropriate error.
|
||||
if (operation && this.willExecuteOperation) {
|
||||
this.willExecuteOperation(operation);
|
||||
}
|
||||
|
||||
// FIXME: If we want to guarantee an operation has been set when invoking
|
||||
// `executionDidStart`, we need to throw an error above and not leave this
|
||||
// to `buildExecutionContext` in `graphql-js`.
|
||||
requestContext.operation = operation || undefined;
|
||||
// We'll set `operationName` to `null` for anonymous operations.
|
||||
requestContext.operationName =
|
||||
(operation && operation.name && operation.name.value) || null;
|
||||
|
||||
await dispatcher.invokeAsync(
|
||||
'didResolveOperation',
|
||||
requestContext as WithRequired<
|
||||
typeof requestContext,
|
||||
'document' | 'operation' | 'operationName'
|
||||
>,
|
||||
);
|
||||
|
||||
const executionDidEnd = await dispatcher.invokeDidStart(
|
||||
'executionDidStart',
|
||||
requestContext as WithRequired<
|
||||
|
|
|
@ -45,7 +45,8 @@ export interface GraphQLRequestContext<TContext = Record<string, any>> {
|
|||
readonly context: TContext;
|
||||
readonly cache: KeyValueCache;
|
||||
|
||||
document?: DocumentNode;
|
||||
readonly document?: DocumentNode;
|
||||
|
||||
// `operationName` is set based on the operation AST, so it is defined
|
||||
// even if no `request.operationName` was passed in.
|
||||
// It will be set to `null` for an anonymous operation.
|
||||
|
|
|
@ -180,7 +180,6 @@ export async function processHTTPRequest<TContext>(
|
|||
},
|
||||
httpRequest: HttpQueryRequest,
|
||||
): Promise<HttpQueryResponse> {
|
||||
let isGetRequest: boolean = false;
|
||||
let requestPayload;
|
||||
|
||||
switch (httpRequest.method) {
|
||||
|
@ -199,7 +198,6 @@ export async function processHTTPRequest<TContext>(
|
|||
throw new HttpQueryError(400, 'GET query missing.');
|
||||
}
|
||||
|
||||
isGetRequest = true;
|
||||
requestPayload = httpRequest.query;
|
||||
break;
|
||||
|
||||
|
@ -216,6 +214,32 @@ export async function processHTTPRequest<TContext>(
|
|||
|
||||
const requestPipeline = new GraphQLRequestPipeline<TContext>(options);
|
||||
|
||||
// GET operations should only be queries (not mutations). We want to throw
|
||||
// a particular HTTP error in that case.
|
||||
requestPipeline.plugins.push({
|
||||
requestDidStart() {
|
||||
return {
|
||||
didResolveOperation({ request, operation }) {
|
||||
if (!request.http) return;
|
||||
|
||||
if (
|
||||
request.http.method === 'GET' &&
|
||||
operation.operation !== 'query'
|
||||
) {
|
||||
throw new HttpQueryError(
|
||||
405,
|
||||
`GET supports only query operation`,
|
||||
false,
|
||||
{
|
||||
Allow: 'POST',
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function buildRequestContext(
|
||||
request: GraphQLRequest,
|
||||
): GraphQLRequestContext<TContext> {
|
||||
|
@ -238,23 +262,6 @@ export async function processHTTPRequest<TContext>(
|
|||
};
|
||||
}
|
||||
|
||||
// GET operations should only be queries (not mutations). We want to throw
|
||||
// a particular HTTP error in that case.
|
||||
if (isGetRequest) {
|
||||
requestPipeline.willExecuteOperation = operation => {
|
||||
if (operation.operation !== 'query') {
|
||||
throw new HttpQueryError(
|
||||
405,
|
||||
`GET supports only query operation`,
|
||||
false,
|
||||
{
|
||||
Allow: 'POST',
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const responseInit: ApolloServerHttpResponse = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
@ -17,15 +17,18 @@ export type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|||
export type DidEndHook<TArgs extends any[]> = (...args: TArgs) => void;
|
||||
|
||||
export interface GraphQLRequestListener<TContext = Record<string, any>> {
|
||||
prepareRequest?(
|
||||
requestContext: GraphQLRequestContext<TContext>,
|
||||
): ValueOrPromise<void>;
|
||||
parsingDidStart?(
|
||||
requestContext: GraphQLRequestContext<TContext>,
|
||||
): DidEndHook<[Error?]> | void;
|
||||
validationDidStart?(
|
||||
requestContext: WithRequired<GraphQLRequestContext<TContext>, 'document'>,
|
||||
): DidEndHook<[ReadonlyArray<Error>?]> | void;
|
||||
didResolveOperation?(
|
||||
requestContext: WithRequired<
|
||||
GraphQLRequestContext<TContext>,
|
||||
'document' | 'operationName' | 'operation'
|
||||
>,
|
||||
): ValueOrPromise<void>;
|
||||
executionDidStart?(
|
||||
requestContext: WithRequired<
|
||||
GraphQLRequestContext<TContext>,
|
||||
|
|
Loading…
Add table
Reference in a new issue