diff --git a/src/__tests__/http-test.ts b/src/__tests__/http-test.ts index 6a2b3218..c92b5f26 100644 --- a/src/__tests__/http-test.ts +++ b/src/__tests__/http-test.ts @@ -1029,6 +1029,34 @@ function runTests(server: Server) { }); }); + it('allow custom graphql params extraction from request', async () => { + const app = server(); + + app.get( + urlString(), + graphqlHTTP({ + schema: TestSchema, + customGraphQLParamsFn: (request) => { + const searchParams = new URLSearchParams(request.url.split('?')[1]); + return { + query: searchParams.get('graphqlQuery'), + variables: null, + operationName: null, + raw: false, + }; + }, + }), + ); + + const response = await app.request().get( + urlString({ + graphqlQuery: '{test}', + }), + ); + + expect(response.text).to.equal('{"data":{"test":"Hello World"}}'); + }); + describe('Pretty printing', () => { it('supports pretty printing', async () => { const app = server(); diff --git a/src/index.ts b/src/index.ts index b04fdb06..688c6fca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -109,6 +109,14 @@ export interface OptionsData { */ customParseFn?: (source: Source) => DocumentNode; + /** + * An optional function which will get params from request instead of + * default getGraphQLParams + */ + customGraphQLParamsFn?: ( + request: Request, + ) => GraphQLParams | Promise; + /** * `formatError` is deprecated and replaced by `customFormatErrorFn`. It will * be removed in version 1.0.0. @@ -206,29 +214,34 @@ export function graphqlHTTP(options: Options): Middleware { let params: GraphQLParams | undefined; let showGraphiQL = false; let graphiqlOptions; - let formatErrorFn = formatError; - let pretty = false; let result: ExecutionResult; let optionsData: OptionsData | undefined; try { - // Parse the Request to get GraphQL request parameters. - try { - params = await getGraphQLParams(request); - } catch (error: unknown) { - // When we failed to parse the GraphQL parameters, we still need to get - // the options object, so make an options call to resolve just that. - optionsData = await resolveOptions(); - pretty = optionsData.pretty ?? false; - formatErrorFn = - optionsData.customFormatErrorFn ?? - optionsData.formatError ?? - formatErrorFn; - throw error; + if (typeof options === 'function') { + try { + // Parse the Request to get GraphQL request parameters. + params = await getGraphQLParams(request); + } catch (error: unknown) { + // When we failed to parse the GraphQL parameters, we still need to get + // the options object, so make an options call to resolve just that. + optionsData = await options(request, response); + validateOptions(); + throw error; + } + optionsData = await options(request, response, params); + validateOptions(); + } else { + optionsData = await options; + validateOptions(); } - // Then, resolve the Options to get OptionsData. - optionsData = await resolveOptions(params); + if (optionsData.customGraphQLParamsFn) { + params = await optionsData.customGraphQLParamsFn(request); + } else if (!params) { + // Parse the Request to get GraphQL request parameters. + params = await getGraphQLParams(request); + } // Collect information from the options data object. const schema = optionsData.schema; @@ -243,12 +256,6 @@ export function graphqlHTTP(options: Options): Middleware { const executeFn = optionsData.customExecuteFn ?? execute; const validateFn = optionsData.customValidateFn ?? validate; - pretty = optionsData.pretty ?? false; - formatErrorFn = - optionsData.customFormatErrorFn ?? - optionsData.formatError ?? - formatErrorFn; - // Assert that schema is required. devAssert( schema != null, @@ -374,6 +381,7 @@ export function graphqlHTTP(options: Options): Middleware { rawError instanceof Error ? rawError : String(rawError), ); + // eslint-disable-next-line require-atomic-updates response.statusCode = error.status; const { headers } = error; @@ -398,6 +406,12 @@ export function graphqlHTTP(options: Options): Middleware { } } + const pretty = optionsData?.pretty ?? false; + const formatErrorFn = + optionsData?.customFormatErrorFn ?? + optionsData?.formatError ?? + formatError; + if (result.errors != null || result.data == null) { const handleRuntimeQueryErrorFn = optionsData?.handleRuntimeQueryErrorFn ?? @@ -442,28 +456,18 @@ export function graphqlHTTP(options: Options): Middleware { sendResponse(response, 'application/json', payload); } - async function resolveOptions( - requestParams?: GraphQLParams, - ): Promise { - const optionsResult = await Promise.resolve( - typeof options === 'function' - ? options(request, response, requestParams) - : options, - ); - + function validateOptions() { devAssert( - optionsResult != null && typeof optionsResult === 'object', + optionsData != null && typeof optionsData === 'object', 'GraphQL middleware option function must return an options object or a promise which will be resolved to an options object.', ); - if (optionsResult.formatError) { + if (optionsData.formatError) { // eslint-disable-next-line no-console console.warn( '`formatError` is deprecated and replaced by `customFormatErrorFn`. It will be removed in version 1.0.0.', ); } - - return optionsResult; } }; }