From 4d185dde4d5b26ad36aa0290d4ebbbb0e079230f Mon Sep 17 00:00:00 2001 From: Guido D'Orsi Date: Thu, 16 Dec 2021 15:10:38 +0100 Subject: [PATCH] feat: upgrade to graphql 16 --- examples/graphql-shield-authorization.js | 126 ---------------------- index.js | 23 ++-- lib/errors.js | 47 +++----- lib/federation.js | 1 + lib/gateway/make-resolver.js | 2 +- lib/subscription-connection.js | 10 +- package.json | 6 +- tap-snapshots/test/federation.js.test.cjs | 1 - test/subscription-connection.js | 4 +- 9 files changed, 40 insertions(+), 180 deletions(-) delete mode 100644 examples/graphql-shield-authorization.js diff --git a/examples/graphql-shield-authorization.js b/examples/graphql-shield-authorization.js deleted file mode 100644 index 9a49aea9..00000000 --- a/examples/graphql-shield-authorization.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict' - -const Fastify = require('fastify') -const mercurius = require('..') -const { makeExecutableSchema } = require('@graphql-tools/schema') -// See https://github.com/maticzav/graphql-shield for docs and additional examples -const { rule, shield, and, or, not } = require('graphql-shield') -const { applyMiddleware } = require('graphql-middleware') - -const app = Fastify() - -const typeDefs = ` - type Query { - frontPage: [Fruit!]! - fruits: [Fruit!]! - customers: [Customer!]! - } - type Mutation { - addFruitToBasket: Boolean! - } - type Fruit { - name: String! - count: Int! - } - type Customer { - id: ID! - basket: [Fruit!]! - } - -` - -const resolvers = { - Query: { - frontPage: () => [ - { name: 'orange', count: 10 }, - { name: 'apple', count: 1 } - ], - fruits: () => [ - { name: 'orange', count: 10 }, - { name: 'apple', count: 1 }, - { name: 'strawberries', count: 100 } - ], - customers: () => [ - { id: 1, basket: [{ name: 'orange', count: 1 }] }, - { id: 2, basket: [{ name: 'apple', count: 2 }] } - ] - }, - Mutation: { - addFruitToBasket: () => true - } -} - -// Auth -const users = { - alice: { - id: 1, - name: 'Alice', - role: 'admin' - }, - bob: { - id: 2, - name: 'Bob', - role: 'editor' - }, - johnny: { - id: 3, - name: 'Johnny', - role: 'customer' - } -} - -function getUser (req) { - const auth = req.headers.authorization - if (users[auth]) { - return users[auth] - } else { - return null - } -} - -// Rules -const isAuthenticated = rule({ cache: 'contextual' })( - async (parent, args, ctx, info) => { - return ctx.user !== null - } -) - -const isAdmin = rule({ cache: 'contextual' })( - async (parent, args, ctx, info) => { - return ctx.user.role === 'admin' - } -) - -const isEditor = rule({ cache: 'contextual' })( - async (parent, args, ctx, info) => { - return ctx.user.role === 'editor' - } -) - -// Permissions -const permissions = shield({ - Query: { - frontPage: not(isAuthenticated), // Note: remove authorization header; public query - fruits: and(isAuthenticated, or(isAdmin, isEditor)), - customers: and(isAuthenticated, isAdmin) - }, - Mutation: { - addFruitToBasket: isAuthenticated - }, - Customer: isAdmin -}) - -const schema = makeExecutableSchema({ typeDefs, resolvers }) - -const schemaWithMiddleware = applyMiddleware(schema, permissions) - -app.register(mercurius, { - schema: schemaWithMiddleware, - graphiql: true, - context: (req) => ({ - ...req, - user: getUser(req) - }) -}) - -app.listen(3000) diff --git a/index.js b/index.js index 5d0f0bea..e979b3f0 100644 --- a/index.js +++ b/index.js @@ -535,7 +535,14 @@ const plugin = fp(async function (app, opts) { const shouldCompileJit = cached && cached.count++ === minJit // Validate variables if (variables !== undefined && !shouldCompileJit) { - const executionContext = buildExecutionContext(fastifyGraphQl.schema, document, root, context, variables, operationName) + const executionContext = buildExecutionContext({ + schema: fastifyGraphQl.schema, + document, + rootValue: root, + contextValue: context, + variableValues: variables, + operationName + }) if (Array.isArray(executionContext)) { const err = new MER_ERR_GQL_VALIDATION() err.errors = executionContext @@ -566,14 +573,14 @@ const plugin = fp(async function (app, opts) { return maybeFormatErrors(execution, context) } - const execution = await execute( - modifiedSchema || fastifyGraphQl.schema, - modifiedDocument || document, - root, - context, - variables, + const execution = await execute({ + schema: modifiedSchema || fastifyGraphQl.schema, + document: modifiedDocument || document, + rootValue: root, + contextValue: context, + variableValues: variables, operationName - ) + }) return maybeFormatErrors(execution, context) } diff --git a/lib/errors.js b/lib/errors.js index 58d934fd..02a7bde0 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -26,39 +26,20 @@ class FederatedError extends Error { // allows to copy the `path` & `locations` properties // from the already serialized error function toGraphQLError (err) { - return Object.create(GraphQLError, { - name: { - value: err.name - }, - message: { - value: err.message, - enumerable: true, - writable: true - }, - locations: { - value: err.locations || undefined, - enumerable: true - }, - path: { - value: err.path || undefined, - enumerable: true - }, - nodes: { - value: err.nodes || undefined - }, - source: { - value: err.source || undefined - }, - positions: { - value: err.positions || undefined - }, - originalError: { - value: err.originalError || undefined - }, - extensions: { - value: err.extensions || undefined - } - }) + const gqlError = new GraphQLError( + err.message, + err.nodes, + err.source, + err.positions, + err.path, + err.originalError, + err.extensions + ) + + gqlError.locations = err.locations + gqlError.name = err.name + + return gqlError } function defaultErrorFormatter (err, ctx) { diff --git a/lib/federation.js b/lib/federation.js index b5bcf534..6970a19b 100644 --- a/lib/federation.js +++ b/lib/federation.js @@ -129,6 +129,7 @@ function getStubTypes (schemaDefinitions, isGateway) { function gatherDirectives (type) { let directives = [] + /* istanbul ignore else seems that extensionASTNodes is always provided in graphql16+, we are keeping this check for safety */ if (type.extensionASTNodes) { for (const node of type.extensionASTNodes) { /* istanbul ignore else we are not interested in nodes that does not have directives */ diff --git a/lib/gateway/make-resolver.js b/lib/gateway/make-resolver.js index db47537f..dd6bc3bc 100644 --- a/lib/gateway/make-resolver.js +++ b/lib/gateway/make-resolver.js @@ -39,7 +39,7 @@ function getDirectiveSelection (node, directiveName) { } function getDirectiveRequiresSelection (selections, type) { - if (!type.extensionASTNodes || + if (!type.extensionASTNodes || type.extensionASTNodes.length === 0 || !type.extensionASTNodes[0].fields[0] || !type.extensionASTNodes[0].fields[0].directives[0]) { return [] diff --git a/lib/subscription-connection.js b/lib/subscription-connection.js index 6911cef1..e787f9c0 100644 --- a/lib/subscription-connection.js +++ b/lib/subscription-connection.js @@ -183,11 +183,11 @@ module.exports = class SubscriptionConnection { subscriptionLoaders = this.fastify[kSubscriptionFactory] } - const subIter = await subscribe( + const subIter = await subscribe({ schema, document, - {}, // rootValue - { + rootValue: {}, + contextValue: { ...context, get __currentQuery () { return print(document) @@ -201,9 +201,9 @@ module.exports = class SubscriptionConnection { [kEntityResolvers]: this.entityResolvers } }, - variables, + variableValues: variables, operationName - ) + }) this.subscriptionIters.set(id, subIter) if (subIter.errors) { diff --git a/package.json b/package.json index 0511e20c..4ce059ee 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "homepage": "https://mercurius.dev", "peerDependencies": { - "graphql": "^15.5.1" + "graphql": "^16.0.0" }, "devDependencies": { "@graphql-tools/merge": "^8.0.0", @@ -41,8 +41,6 @@ "concurrently": "^6.2.0", "docsify-cli": "^4.4.3", "fastify": "^3.18.1", - "graphql-middleware": "^6.0.10", - "graphql-shield": "^7.5.0", "graphql-tools": "^8.0.0", "pre-commit": "^1.2.2", "proxyquire": "^2.1.3", @@ -62,7 +60,7 @@ "fastify-plugin": "^3.0.0", "fastify-static": "^4.2.2", "fastify-websocket": "^4.0.0", - "graphql": "^15.5.1", + "graphql": "^16.0.0", "graphql-jit": "^0.7.0", "mqemitter": "^4.4.1", "p-map": "^4.0.0", diff --git a/tap-snapshots/test/federation.js.test.cjs b/tap-snapshots/test/federation.js.test.cjs index 29f108e8..5a562ef2 100644 --- a/tap-snapshots/test/federation.js.test.cjs +++ b/tap-snapshots/test/federation.js.test.cjs @@ -43,5 +43,4 @@ type User { } union _Entity = Product | User - ` diff --git a/test/subscription-connection.js b/test/subscription-connection.js index efb8b42b..c9c1e7c1 100644 --- a/test/subscription-connection.js +++ b/test/subscription-connection.js @@ -295,7 +295,7 @@ test('subscription connection send error message when client message type is inv })) }) -test('subscription connection handles GQL_START message correctly, when payload.query is not defined', async (t) => { +test('subscription connection handles GQL_START message correctly, when payload.query is not defined', (t) => { t.plan(1) const sc = new SubscriptionConnection({ @@ -311,7 +311,7 @@ test('subscription connection handles GQL_START message correctly, when payload. sc.isReady = true - await sc.handleMessage(JSON.stringify({ + sc.handleMessage(JSON.stringify({ id: 1, type: 'start', payload: { }