From abb0d322e6c32b18d977810a7e1af9814b92de44 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Fri, 23 Jul 2021 12:25:21 +0100 Subject: [PATCH 1/3] Manually add schemaDirectives after resolvers --- packages/graphql/package.json | 2 +- packages/graphql/src/classes/Neo4jGraphQL.ts | 15 +- .../tests/integration/issues/349.int.test.ts | 196 ++++++++++++++++++ yarn.lock | 17 +- 4 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 packages/graphql/tests/integration/issues/349.int.test.ts diff --git a/packages/graphql/package.json b/packages/graphql/package.json index f1735fa3a9..77f3a5eae1 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -36,7 +36,6 @@ }, "author": "Neo4j Inc.", "devDependencies": { - "@graphql-tools/utils": "^7.7.3", "@types/deep-equal": "1.0.1", "@types/faker": "5.1.7", "@types/is-uuid": "1.0.0", @@ -63,6 +62,7 @@ "dependencies": { "@graphql-tools/merge": "^6.2.13", "@graphql-tools/schema": "^7.1.3", + "@graphql-tools/utils": "^7.10.0", "camelcase": "^6.2.0", "debug": "^4.3.1", "deep-equal": "^2.0.5", diff --git a/packages/graphql/src/classes/Neo4jGraphQL.ts b/packages/graphql/src/classes/Neo4jGraphQL.ts index fead4bd75a..de6e8c89bb 100644 --- a/packages/graphql/src/classes/Neo4jGraphQL.ts +++ b/packages/graphql/src/classes/Neo4jGraphQL.ts @@ -21,6 +21,7 @@ import Debug from "debug"; import { Driver } from "neo4j-driver"; import { DocumentNode, GraphQLResolveInfo, GraphQLSchema, parse, printSchema, print } from "graphql"; import { addResolversToSchema, addSchemaLevelResolver, IExecutableSchemaDefinition } from "@graphql-tools/schema"; +import { SchemaDirectiveVisitor } from "@graphql-tools/utils"; import type { DriverConfig, CypherQueryOptions } from "../types"; import { makeAugmentedSchema } from "../schema"; import Node from "./Node"; @@ -46,9 +47,10 @@ export interface Neo4jGraphQLConfig { queryOptions?: CypherQueryOptions; } -export interface Neo4jGraphQLConstructor extends IExecutableSchemaDefinition { +export interface Neo4jGraphQLConstructor extends Omit { config?: Neo4jGraphQLConfig; driver?: Driver; + schemaDirectives?: Record; } class Neo4jGraphQL { @@ -65,7 +67,7 @@ class Neo4jGraphQL { public config?: Neo4jGraphQLConfig; constructor(input: Neo4jGraphQLConstructor) { - const { config = {}, driver, resolvers, ...schemaDefinition } = input; + const { config = {}, driver, resolvers, schemaDirectives, ...schemaDefinition } = input; const { nodes, relationships, schema } = makeAugmentedSchema(schemaDefinition, { enableRegex: config.enableRegex, }); @@ -87,6 +89,15 @@ class Neo4jGraphQL { this.schema = addResolversToSchema(this.schema, resolvers); } } + + if (schemaDirectives) { + SchemaDirectiveVisitor.visitSchemaDirectives( + this.schema, + schemaDirectives + // (schemaDirectives as unknown) as Record + ); + } + this.schema = this.createWrappedSchema({ schema: this.schema, config }); this.document = parse(printSchema(schema)); } diff --git a/packages/graphql/tests/integration/issues/349.int.test.ts b/packages/graphql/tests/integration/issues/349.int.test.ts new file mode 100644 index 0000000000..26de0c5de4 --- /dev/null +++ b/packages/graphql/tests/integration/issues/349.int.test.ts @@ -0,0 +1,196 @@ +import neo4j from "neo4j-driver"; +import { SchemaDirectiveVisitor } from "@graphql-tools/utils"; +import { graphql } from "graphql"; +import { Neo4jGraphQL } from "../../../src/classes"; + +describe("https://github.com/neo4j/graphql/issues/349", () => { + type Field = Parameters[0]; + + class DisallowDirective extends SchemaDirectiveVisitor { + // eslint-disable-next-line class-methods-use-this + public visitFieldDefinition(field: Field) { + field.resolve = function () { + // Disallow any and all access, all the time + throw new Error("go away"); + }; + } + } + + const schemaDirectives = { + disallow: DisallowDirective, + }; + + describe("https://github.com/neo4j/graphql/issues/349#issuecomment-885295157", () => { + const neo4jGraphQL = new Neo4jGraphQL({ + typeDefs: /* GraphQL */ ` + directive @disallow on FIELD_DEFINITION + + type Mutation { + doStuff: String! @disallow + } + + type Query { + noop: Boolean + } + `, + + driver: neo4j.driver("bolt://localhost:7687"), + resolvers: { Mutation: { doStuff: () => "OK" } }, + schemaDirectives, + }); + + test("DisallowDirective", async () => { + const gqlResult = await graphql({ + schema: neo4jGraphQL.schema, + source: /* GraphQL */ ` + mutation { + doStuff + } + `, + contextValue: { driver: neo4j.driver("bolt://localhost:7687") }, + }); + + expect(gqlResult.data).toBeNull(); + expect(gqlResult.errors).toBeTruthy(); + }); + }); + + describe("https://github.com/neo4j/graphql/issues/349#issuecomment-885311918", () => { + const neo4jGraphQL = new Neo4jGraphQL({ + typeDefs: /* GraphQL */ ` + directive @disallow on FIELD_DEFINITION + + type NestedResult { + stuff: String! @disallow + } + + type Mutation { + doStuff: String! @disallow + doNestedStuff: NestedResult! + } + + type Query { + getStuff: String! @disallow + getNestedStuff: NestedResult! + } + `, + + driver: neo4j.driver("bolt://localhost:7687"), + resolvers: { + NestedResult: { + stuff: (parent: string) => parent, + }, + + Mutation: { + doStuff: () => "OK", + doNestedStuff: () => "OK", + }, + + Query: { + getStuff: () => "OK", + getNestedStuff: () => "OK", + }, + }, + schemaDirectives, + }); + + test("mutation top - DisallowDirective", async () => { + const gqlResult = await graphql({ + schema: neo4jGraphQL.schema, + source: /* GraphQL */ ` + mutation { + doStuff + } + `, + contextValue: { driver: neo4j.driver("bolt://localhost:7687") }, + }); + + expect(gqlResult.data).toBeNull(); + expect(gqlResult.errors && gqlResult.errors[0].message).toBe("go away"); + }); + + test("query top - DisallowDirective", async () => { + const gqlResult = await graphql({ + schema: neo4jGraphQL.schema, + source: /* GraphQL */ ` + query { + getStuff + } + `, + contextValue: { driver: neo4j.driver("bolt://localhost:7687") }, + }); + + expect(gqlResult.data).toBeNull(); + expect(gqlResult.errors && gqlResult.errors[0].message).toBe("go away"); + }); + + test("mutation nested - DisallowDirective", async () => { + const gqlResult = await graphql({ + schema: neo4jGraphQL.schema, + source: /* GraphQL */ ` + mutation { + doNestedStuff { + stuff + } + } + `, + contextValue: { driver: neo4j.driver("bolt://localhost:7687") }, + }); + + expect(gqlResult.data).toBeNull(); + expect(gqlResult.errors && gqlResult.errors[0].message).toBe("go away"); + }); + + test("query nested - DisallowDirective", async () => { + const gqlResult = await graphql({ + schema: neo4jGraphQL.schema, + source: /* GraphQL */ ` + query { + getNestedStuff { + stuff + } + } + `, + contextValue: { driver: neo4j.driver("bolt://localhost:7687") }, + }); + + expect(gqlResult.data).toBeNull(); + expect(gqlResult.errors && gqlResult.errors[0].message).toBe("go away"); + }); + }); + + describe("schemaDirectives can be an empty object", () => { + const neo4jGraphQL = new Neo4jGraphQL({ + typeDefs: /* GraphQL */ ` + directive @disallow on FIELD_DEFINITION + + type Mutation { + doStuff: String! @disallow + } + + type Query { + noop: Boolean + } + `, + + driver: neo4j.driver("bolt://localhost:7687"), + resolvers: { Mutation: { doStuff: () => "OK" } }, + schemaDirectives: {}, + }); + + test("DisallowDirective", async () => { + const gqlResult = await graphql({ + schema: neo4jGraphQL.schema, + source: /* GraphQL */ ` + mutation { + doStuff + } + `, + contextValue: { driver: neo4j.driver("bolt://localhost:7687") }, + }); + + expect(gqlResult.data?.doStuff).toEqual("OK"); + expect(gqlResult.errors).toBeFalsy(); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 3587d4f95b..5224e65470 100644 --- a/yarn.lock +++ b/yarn.lock @@ -624,7 +624,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:^7.1.2, @graphql-tools/utils@npm:^7.7.0, @graphql-tools/utils@npm:^7.7.3": +"@graphql-tools/utils@npm:^7.1.2, @graphql-tools/utils@npm:^7.7.0": version: 7.8.0 resolution: "@graphql-tools/utils@npm:7.8.0" dependencies: @@ -637,6 +637,19 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/utils@npm:^7.10.0": + version: 7.10.0 + resolution: "@graphql-tools/utils@npm:7.10.0" + dependencies: + "@ardatan/aggregate-error": 0.0.6 + camel-case: 4.1.2 + tslib: ~2.2.0 + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 + checksum: 8b8e674f344e825c27816ead8a66edca5aed2eac26b601bb028350837ae39fee3ca9241255b918b8199d60bff41884a4814f98df465a0a8a5db5b91428ce2aa9 + languageName: node + linkType: hard + "@graphql-typed-document-node/core@npm:^3.0.0": version: 3.1.0 resolution: "@graphql-typed-document-node/core@npm:3.1.0" @@ -929,7 +942,7 @@ __metadata: dependencies: "@graphql-tools/merge": ^6.2.13 "@graphql-tools/schema": ^7.1.3 - "@graphql-tools/utils": ^7.7.3 + "@graphql-tools/utils": ^7.10.0 "@types/deep-equal": 1.0.1 "@types/faker": 5.1.7 "@types/is-uuid": 1.0.0 From 6ef534772cc104055a79d9d52e500486308c4f16 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Fri, 23 Jul 2021 12:29:43 +0100 Subject: [PATCH 2/3] Add missing copyright header --- .../tests/integration/issues/349.int.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/graphql/tests/integration/issues/349.int.test.ts b/packages/graphql/tests/integration/issues/349.int.test.ts index 26de0c5de4..5d2bbd1541 100644 --- a/packages/graphql/tests/integration/issues/349.int.test.ts +++ b/packages/graphql/tests/integration/issues/349.int.test.ts @@ -1,3 +1,22 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import neo4j from "neo4j-driver"; import { SchemaDirectiveVisitor } from "@graphql-tools/utils"; import { graphql } from "graphql"; From e48796e3edc8229cb6e9fbbe76e91d6f1d6d084a Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Fri, 23 Jul 2021 15:11:35 +0100 Subject: [PATCH 3/3] Remove leftover comment, add some explanation to order --- packages/graphql/src/classes/Neo4jGraphQL.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/graphql/src/classes/Neo4jGraphQL.ts b/packages/graphql/src/classes/Neo4jGraphQL.ts index de6e8c89bb..dd3e12a264 100644 --- a/packages/graphql/src/classes/Neo4jGraphQL.ts +++ b/packages/graphql/src/classes/Neo4jGraphQL.ts @@ -77,8 +77,15 @@ class Neo4jGraphQL { this.nodes = nodes; this.relationships = relationships; this.schema = schema; + /* - addResolversToSchema must be first, so that custom resolvers also get schema level resolvers + Order must be: + + addResolversToSchema -> visitSchemaDirectives -> createWrappedSchema + + addResolversToSchema breaks schema directives added before it + + createWrappedSchema must come last so that all requests have context prepared correctly */ if (resolvers) { if (Array.isArray(resolvers)) { @@ -91,11 +98,7 @@ class Neo4jGraphQL { } if (schemaDirectives) { - SchemaDirectiveVisitor.visitSchemaDirectives( - this.schema, - schemaDirectives - // (schemaDirectives as unknown) as Record - ); + SchemaDirectiveVisitor.visitSchemaDirectives(this.schema, schemaDirectives); } this.schema = this.createWrappedSchema({ schema: this.schema, config });