From 75257590db5177346ce2e6849d9f92a9d09a1113 Mon Sep 17 00:00:00 2001 From: "Siddharth Gelera (reaper)" Date: Fri, 16 Feb 2024 19:26:41 +0530 Subject: [PATCH] Feat: add the ability to add in additional route properties (#1080) * feat: add in route properties * tests(route): add tests for additionalRouteProp * fix(routes): normalize additionalProps before injection * docs: add type information for options * tests(routes): add more description for the ts linter * docs(api): add `additionalRouteProps` to options api * docs(typedocs): fix grammatical error * fix: change name to `additionalRouteOptions` * fix: type tests for `additionalRouteOptions` also relevant changes to the types for the same --- docs/api/options.md | 1 + index.d.ts | 13 ++++++ index.js | 3 +- lib/routes.js | 13 +++++- test/routes.js | 97 +++++++++++++++++++++++++++++++++++++++++++++ test/types/index.ts | 7 ++++ 6 files changed, 131 insertions(+), 3 deletions(-) diff --git a/docs/api/options.md b/docs/api/options.md index 5bc4d451..5e62e397 100644 --- a/docs/api/options.md +++ b/docs/api/options.md @@ -89,6 +89,7 @@ - `allowBatchedQueries`: Boolean. Flag to control whether to allow batched queries. When `true`, the server supports recieving an array of queries and returns an array of results. - `compilerOptions`: Object. Configurable options for the graphql-jit compiler. For more details check https://github.com/zalando-incubator/graphql-jit +- `additionalRouteOptions`: Object. Takes similar configuration to the Route Options of Fastify. Use cases include being able to add constraints, modify validation schema, increase the `bodyLimit`, etc. You can read more about the possible values on [fastify.dev's Routes Options](https://fastify.dev/docs/latest/Reference/Routes/#options) #### queryDepth example diff --git a/index.d.ts b/index.d.ts index b209c1bd..2610c694 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,6 +3,7 @@ import { FastifyReply, FastifyRequest, FastifyInstance, + RouteOptions } from "fastify"; import { DocumentNode, @@ -487,6 +488,18 @@ declare namespace mercurius { * receive an array of responses within a single request. */ allowBatchedQueries?: boolean; + + /** + * Customize the graphql routers initialized by mercurius with + * more fastify route options. + * + * Usecases: + * - Add more validation + * - change the schema structure + * - Hook on the request's life cycle + * - Increase body size limit for larger queries + */ + additionalRouteOptions?:Omit } export type MercuriusOptions = MercuriusCommonOptions & (MercuriusSchemaOptions) diff --git a/index.js b/index.js index e1b89de0..4523237d 100644 --- a/index.js +++ b/index.js @@ -210,7 +210,8 @@ const mercurius = fp(async function (app, opts) { entityResolversFactory: undefined, subscriptionContextFn, keepAlive, - fullWsTransport + fullWsTransport, + additionalRouteOptions: opts.additionalRouteOptions }) } diff --git a/lib/routes.js b/lib/routes.js index a6ab508b..9d5d7985 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -200,7 +200,8 @@ module.exports = async function (app, opts) { persistedQueryProvider, allowBatchedQueries, keepAlive, - fullWsTransport + fullWsTransport, + additionalRouteOptions } = opts // Load the persisted query settings @@ -214,6 +215,12 @@ module.exports = async function (app, opts) { notSupportedError } = persistedQueryProvider || {} + const normalizedRouteOptions = { ...additionalRouteOptions } + if (normalizedRouteOptions.handler || normalizedRouteOptions.wsHandler) { + normalizedRouteOptions.handler = undefined + normalizedRouteOptions.wsHandler = undefined + } + async function executeQuery (query, variables, operationName, request, reply) { // Validate a query is present if (!query) { @@ -293,6 +300,7 @@ module.exports = async function (app, opts) { method: 'GET', schema: getSchema, attachValidation: true, + ...normalizedRouteOptions, handler: async function (request, reply) { // Generate the context for this request if (contextFn) { @@ -338,7 +346,8 @@ module.exports = async function (app, opts) { app.post(graphqlPath, { schema: postSchema(allowBatchedQueries), - attachValidation: true + attachValidation: true, + ...normalizedRouteOptions }, async function (request, reply) { // Generate the context for this request if (contextFn) { diff --git a/test/routes.js b/test/routes.js index cbff0e64..82f862f1 100644 --- a/test/routes.js +++ b/test/routes.js @@ -2187,3 +2187,100 @@ test('if ide has plugin set, serve config.js with the correct endpoint', async ( t.equal(res.body.toString(), 'window.GRAPHQL_ENDPOINT = \'/something/app/graphql\';\n' + 'window.GRAPHIQL_PLUGIN_LIST = []') }) + +test('GET graphql endpoint with invalid additionalRouteProp constraint', async (t) => { + const app = Fastify() + const schema = ` + type Query { + add(x: Int, y: Int): Int + } + ` + app.register(GQL, { + schema, + additionalRouteOptions: { + constraints: { + host: 'auth.fastify.dev' + } + } + }) + + const res = await app.inject({ + method: 'GET', + url: '/graphiql', + headers: { + Host: 'fail.fastify.dev' + } + }) + + t.equal(res.statusCode, 404) +}) + +test('GET graphql endpoint with valid additionalRouteProp constraint', async (t) => { + const app = Fastify() + const schema = ` + type Query { + add(x: Int, y: Int): Int + } + ` + app.register(GQL, { + schema, + additionalRouteOptions: { + constraints: { + host: 'auth.fastify.dev' + } + } + }) + + const query = '{ add(x: 2, y: 2) }' + + const res = await app.inject({ + method: 'GET', + url: '/graphql', + headers: { + Host: 'auth.fastify.dev', + 'Content-Type': 'text/plain' + }, + query: { + query + } + }) + + t.equal(res.statusCode, 200) +}) + +test('GET graphql endpoint, additionalRouteOptions should ignore custom handler', async (t) => { + const app = Fastify() + const schema = ` + type Query { + add(x: Int, y: Int): Int + } + ` + + let executed = false + + app.register(GQL, { + schema, + additionalRouteOptions: { + // @ts-expect-error `handler` property is ignored in props + handler (req, reply) { + executed = true + } + } + }) + + const query = '{ add(x: 2, y: 2) }' + + const res = await app.inject({ + method: 'GET', + url: '/graphql', + headers: { + 'Content-Type': 'text/plain' + }, + query: { + query + } + }) + + t.equal(res.statusCode, 200) + t.notOk(executed) +}) diff --git a/test/types/index.ts b/test/types/index.ts index 341a9ea0..3c86cde9 100644 --- a/test/types/index.ts +++ b/test/types/index.ts @@ -265,6 +265,13 @@ makeGraphqlServer({ }) makeGraphqlServer({ schema, errorFormatter: mercurius.defaultErrorFormatter }) makeGraphqlServer({ schema: [schema, 'extend type Query { foo: String }'] }) +makeGraphqlServer({ + additionalRouteOptions: { + constraints: { + version: '1.2' + } + } +}) // Subscriptions