From ea9dd4aa80120bb21549c32d9406ca2297c4662a Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 19 Jul 2023 11:01:49 +0100 Subject: [PATCH 1/7] Miscellaneous test fix --- .../graphql/tests/schema/issues/3439.test.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/graphql/tests/schema/issues/3439.test.ts b/packages/graphql/tests/schema/issues/3439.test.ts index 73717e544e..a4e101e297 100644 --- a/packages/graphql/tests/schema/issues/3439.test.ts +++ b/packages/graphql/tests/schema/issues/3439.test.ts @@ -21,7 +21,7 @@ import { printSchemaWithDirectives } from "@graphql-tools/utils"; import { gql } from "graphql-tag"; import { lexicographicSortSchema } from "graphql/utilities"; import { Neo4jGraphQL } from "../../../src"; -import { TestSubscriptionsPlugin } from "../../utils/TestSubscriptionPlugin"; +import { TestSubscriptionsMechanism } from "../../utils/TestSubscriptionsMechanism"; import { validateSchema } from "graphql"; describe("https://github.com/neo4j/graphql/issues/3439", () => { @@ -58,8 +58,8 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } `; - const subscriptionPlugin = new TestSubscriptionsPlugin(); - const neoSchema = new Neo4jGraphQL({ typeDefs, plugins: { subscriptions: subscriptionPlugin } }); + const subscriptionsMechanism = new TestSubscriptionsMechanism(); + const neoSchema = new Neo4jGraphQL({ typeDefs, features: { subscriptions: subscriptionsMechanism } }); const schema = await neoSchema.getSchema(); const errors = validateSchema(schema); @@ -80,7 +80,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type CreateInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesCreated: Int! relationshipsCreated: Int! } @@ -96,7 +96,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type DeleteInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesDeleted: Int! relationshipsDeleted: Int! } @@ -1258,7 +1258,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type UpdateInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesCreated: Int! nodesDeleted: Int! relationshipsCreated: Int! @@ -1306,8 +1306,8 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } `; - const subscriptionPlugin = new TestSubscriptionsPlugin(); - const neoSchema = new Neo4jGraphQL({ typeDefs, plugins: { subscriptions: subscriptionPlugin } }); + const subscriptionsMechanism = new TestSubscriptionsMechanism(); + const neoSchema = new Neo4jGraphQL({ typeDefs, features: { subscriptions: subscriptionsMechanism } }); const schema = await neoSchema.getSchema(); const errors = validateSchema(schema); @@ -1328,7 +1328,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type CreateInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesCreated: Int! relationshipsCreated: Int! } @@ -1344,7 +1344,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type DeleteInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesDeleted: Int! relationshipsDeleted: Int! } @@ -2427,7 +2427,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type UpdateInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesCreated: Int! nodesDeleted: Int! relationshipsCreated: Int! @@ -2475,8 +2475,8 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } `; - const subscriptionPlugin = new TestSubscriptionsPlugin(); - const neoSchema = new Neo4jGraphQL({ typeDefs, plugins: { subscriptions: subscriptionPlugin } }); + const subscriptionsMechanism = new TestSubscriptionsMechanism(); + const neoSchema = new Neo4jGraphQL({ typeDefs, features: { subscriptions: subscriptionsMechanism } }); const schema = await neoSchema.getSchema(); const errors = validateSchema(schema); @@ -2497,7 +2497,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type CreateInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesCreated: Int! relationshipsCreated: Int! } @@ -2513,7 +2513,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type DeleteInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesDeleted: Int! relationshipsDeleted: Int! } @@ -3193,7 +3193,7 @@ describe("https://github.com/neo4j/graphql/issues/3439", () => { } type UpdateInfo { - bookmark: String + bookmark: String @deprecated(reason: \\"This field has been deprecated because bookmarks are now handled by the driver.\\") nodesCreated: Int! nodesDeleted: Int! relationshipsCreated: Int! From 5d3e361cce974d2c25e7e25d03ea1722f4e65ede Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 19 Jul 2023 11:01:59 +0100 Subject: [PATCH 2/7] Missing custom resolvers to warn instead of throw --- packages/graphql/src/classes/Neo4jGraphQL.ts | 8 -- .../type-dependant-directives/jwt-payload.ts | 2 - .../schema/get-custom-resolver-meta.test.ts | 128 ++++++----------- .../src/schema/get-custom-resolver-meta.ts | 6 +- packages/graphql/src/schema/get-nodes.ts | 2 - .../graphql/src/schema/get-obj-field-meta.ts | 3 - .../src/schema/make-augmented-schema.test.ts | 4 - .../src/schema/make-augmented-schema.ts | 10 +- .../parse/parse-fulltext-directive.test.ts | 3 - .../startup-validation.int.test.ts | 130 ++---------------- .../directives/customResolver.int.test.ts | 23 +++- 11 files changed, 73 insertions(+), 246 deletions(-) diff --git a/packages/graphql/src/classes/Neo4jGraphQL.ts b/packages/graphql/src/classes/Neo4jGraphQL.ts index b4da2d3630..4dd88222db 100644 --- a/packages/graphql/src/classes/Neo4jGraphQL.ts +++ b/packages/graphql/src/classes/Neo4jGraphQL.ts @@ -61,7 +61,6 @@ export interface Neo4jGraphQLConfig { export type ValidationConfig = { validateTypeDefs: boolean; - validateResolvers: boolean; validateDuplicateRelationshipFields: boolean; }; @@ -76,7 +75,6 @@ export interface Neo4jGraphQLConstructor { export const defaultValidationConfig: ValidationConfig = { validateTypeDefs: true, - validateResolvers: true, validateDuplicateRelationshipFields: true, }; @@ -224,8 +222,6 @@ class Neo4jGraphQL { const { typeDefs } = makeAugmentedSchema(document, { features: this.features, - // enableRegex: false, - validateResolvers: true, generateSubscriptions: true, userCustomResolvers: undefined, }); @@ -388,7 +384,6 @@ class Neo4jGraphQL { const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(document, { features: this.features, - validateResolvers: validationConfig.validateResolvers, generateSubscriptions: Boolean(this.features?.subscriptions), userCustomResolvers: this.resolvers, }); @@ -440,7 +435,6 @@ class Neo4jGraphQL { const { nodes, relationships, typeDefs, resolvers } = makeAugmentedSchema(document, { features: this.features, - validateResolvers: validationConfig.validateResolvers, generateSubscriptions: Boolean(this.features?.subscriptions), userCustomResolvers: this.resolvers, subgraph, @@ -478,14 +472,12 @@ class Neo4jGraphQL { if (this.config?.startupValidation === false) { return { validateTypeDefs: false, - validateResolvers: false, validateDuplicateRelationshipFields: false, }; } if (typeof this.config?.startupValidation === "object") { if (this.config?.startupValidation.typeDefs === false) validationConfig.validateTypeDefs = false; - if (this.config?.startupValidation.resolvers === false) validationConfig.validateResolvers = false; if (this.config?.startupValidation.noDuplicateRelationshipFields === false) validationConfig.validateDuplicateRelationshipFields = false; } diff --git a/packages/graphql/src/graphql/directives/type-dependant-directives/jwt-payload.ts b/packages/graphql/src/graphql/directives/type-dependant-directives/jwt-payload.ts index 4d96de094e..5b7d6076fd 100644 --- a/packages/graphql/src/graphql/directives/type-dependant-directives/jwt-payload.ts +++ b/packages/graphql/src/graphql/directives/type-dependant-directives/jwt-payload.ts @@ -35,7 +35,6 @@ export function getJwtFields( unions: [], scalars: [], enums: [], - validateResolvers: false, }); const standardTypeDefinition = getStandardJwtDefinition(schema); const standardJwtFields = getObjFieldMeta({ @@ -45,7 +44,6 @@ export function getJwtFields( unions: [], scalars: [], enums: [], - validateResolvers: false, }); fields.primitiveFields.push(...standardJwtFields.primitiveFields); return fields; diff --git a/packages/graphql/src/schema/get-custom-resolver-meta.test.ts b/packages/graphql/src/schema/get-custom-resolver-meta.test.ts index 36ffef839f..405baedf47 100644 --- a/packages/graphql/src/schema/get-custom-resolver-meta.test.ts +++ b/packages/graphql/src/schema/get-custom-resolver-meta.test.ts @@ -21,10 +21,9 @@ import type { FieldDefinitionNode, InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode, - UnionTypeDefinitionNode} from "graphql"; -import { - Kind, + UnionTypeDefinitionNode, } from "graphql"; +import { Kind } from "graphql"; import { generateResolveTree } from "../translate/utils/resolveTree"; import { getCustomResolverMeta, INVALID_SELECTION_SET_ERROR } from "./get-custom-resolver-meta"; @@ -459,6 +458,16 @@ describe("getCustomResolverMeta", () => { [customResolverField]: () => 25, }; + let warn: jest.SpyInstance; + + beforeEach(() => { + warn = jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + warn.mockReset(); + }); + test("should return undefined if no directive found", () => { // @ts-ignore const field: FieldDefinitionNode = { @@ -490,12 +499,13 @@ describe("getCustomResolverMeta", () => { field, object, objects, - validateResolvers: true, interfaces, unions, customResolvers: resolvers, }); + expect(warn).not.toHaveBeenCalled(); + expect(result).toBeUndefined(); }); test("should return no required fields if no requires argument", () => { @@ -531,12 +541,13 @@ describe("getCustomResolverMeta", () => { field, object, objects, - validateResolvers: true, interfaces, unions, customResolvers: resolvers, }); + expect(warn).not.toHaveBeenCalled(); + expect(result).toEqual({ requiredFields: {}, }); @@ -586,12 +597,13 @@ describe("getCustomResolverMeta", () => { field, object, objects, - validateResolvers: true, interfaces, unions, customResolvers: resolvers, }); + expect(warn).not.toHaveBeenCalled(); + expect(result).toEqual({ requiredFields: generateResolveTree({ name: requiredFields }), }); @@ -642,12 +654,13 @@ describe("getCustomResolverMeta", () => { field, object, objects, - validateResolvers: true, interfaces, unions, customResolvers: resolvers, }); + expect(warn).not.toHaveBeenCalled(); + expect(result).toEqual({ requiredFields: { name: { @@ -737,7 +750,6 @@ describe("getCustomResolverMeta", () => { field, object, objects, - validateResolvers: true, interfaces, unions, customResolvers: resolvers, @@ -788,17 +800,16 @@ describe("getCustomResolverMeta", () => { const resolvers = {}; - expect(() => - getCustomResolverMeta({ - field, - object, - objects, - validateResolvers: true, - interfaces, - unions, - customResolvers: resolvers, - }) - ).toThrow(`Custom resolver for ${customResolverField} has not been provided`); + getCustomResolverMeta({ + field, + object, + objects, + interfaces, + unions, + customResolvers: resolvers, + }); + + expect(warn).toHaveBeenCalledWith(`Custom resolver for ${customResolverField} has not been provided`); }); test("Check throws error if customResolver defined on interface", () => { const requiredFields = "name"; @@ -847,76 +858,15 @@ describe("getCustomResolverMeta", () => { }, }; - expect(() => - getCustomResolverMeta({ - field, - object, - objects, - validateResolvers: true, - interfaces, - unions, - customResolvers: resolvers, - }) - ).toThrow(`Custom resolver for ${customResolverField} has not been provided`); - }); - - test("Check does not throw error if validateResolvers false", () => { - const requiredFields = "name"; - const field: FieldDefinitionNode = { - directives: [ - { - // @ts-ignore - name: { - value: "customResolver", - // @ts-ignore - }, - arguments: [ - { - // @ts-ignore - name: { value: "requires" }, - // @ts-ignore - value: { - kind: Kind.STRING, - value: requiredFields, - }, - }, - ], - }, - { - // @ts-ignore - name: { value: "RANDOM 2" }, - }, - { - // @ts-ignore - name: { value: "RANDOM 3" }, - }, - { - // @ts-ignore - name: { value: "RANDOM 4" }, - }, - ], - name: { - kind: Kind.NAME, - value: customResolverField, - }, - }; - - const resolvers = { - [publicationInterface]: { - [customResolverField]: () => "Hello World!", - }, - }; + getCustomResolverMeta({ + field, + object, + objects, + interfaces, + unions, + customResolvers: resolvers, + }); - expect(() => - getCustomResolverMeta({ - field, - object, - objects, - validateResolvers: false, - interfaces, - unions, - customResolvers: resolvers, - }) - ).not.toThrow(); + expect(warn).toHaveBeenCalledWith(`Custom resolver for ${customResolverField} has not been provided`); }); }); diff --git a/packages/graphql/src/schema/get-custom-resolver-meta.ts b/packages/graphql/src/schema/get-custom-resolver-meta.ts index ca04851d23..f90c9a016c 100644 --- a/packages/graphql/src/schema/get-custom-resolver-meta.ts +++ b/packages/graphql/src/schema/get-custom-resolver-meta.ts @@ -46,7 +46,6 @@ export function getCustomResolverMeta({ field, object, objects, - validateResolvers, interfaces, unions, customResolvers, @@ -55,7 +54,6 @@ export function getCustomResolverMeta({ field: FieldDefinitionNode; object: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode; objects: ObjectTypeDefinitionNode[]; - validateResolvers: boolean | undefined; interfaces: InterfaceTypeDefinitionNode[]; unions: UnionTypeDefinitionNode[]; customResolvers?: IResolvers | IResolvers[]; @@ -69,8 +67,8 @@ export function getCustomResolverMeta({ return undefined; } - if (validateResolvers && object.kind !== Kind.INTERFACE_TYPE_DEFINITION && !customResolvers?.[field.name.value]) { - throw new Error(`Custom resolver for ${field.name.value} has not been provided`); + if (object.kind !== Kind.INTERFACE_TYPE_DEFINITION && !customResolvers?.[field.name.value]) { + console.warn(`Custom resolver for ${field.name.value} has not been provided`); } const directiveRequiresArgument = directive?.arguments?.find((arg) => arg.name.value === "requires"); diff --git a/packages/graphql/src/schema/get-nodes.ts b/packages/graphql/src/schema/get-nodes.ts index 181ede21c5..62f336c9f8 100644 --- a/packages/graphql/src/schema/get-nodes.ts +++ b/packages/graphql/src/schema/get-nodes.ts @@ -48,7 +48,6 @@ function getNodes( options: { callbacks?: Neo4jGraphQLCallbacks; userCustomResolvers?: IResolvers | Array; - validateResolvers?: boolean; } ): Nodes { let pointInTypeDefs = false; @@ -143,7 +142,6 @@ function getNodes( unions: definitionNodes.unionTypes, callbacks: options.callbacks, customResolvers, - validateResolvers: options.validateResolvers, }); let fulltextDirective: FullText; diff --git a/packages/graphql/src/schema/get-obj-field-meta.ts b/packages/graphql/src/schema/get-obj-field-meta.ts index b750caced3..4cbbd90dee 100644 --- a/packages/graphql/src/schema/get-obj-field-meta.ts +++ b/packages/graphql/src/schema/get-obj-field-meta.ts @@ -90,7 +90,6 @@ function getObjFieldMeta({ enums, callbacks, customResolvers, - validateResolvers, }: { obj: ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode; objects: ObjectTypeDefinitionNode[]; @@ -98,7 +97,6 @@ function getObjFieldMeta({ unions: UnionTypeDefinitionNode[]; scalars: ScalarTypeDefinitionNode[]; enums: EnumTypeDefinitionNode[]; - validateResolvers?: boolean; callbacks?: Neo4jGraphQLCallbacks; customResolvers?: IResolvers | Array; }): ObjectFields { @@ -134,7 +132,6 @@ function getObjFieldMeta({ objects, interfaces, unions, - validateResolvers, customResolvers, interfaceField, }); diff --git a/packages/graphql/src/schema/make-augmented-schema.test.ts b/packages/graphql/src/schema/make-augmented-schema.test.ts index 45a7cb7801..330cd4242c 100644 --- a/packages/graphql/src/schema/make-augmented-schema.test.ts +++ b/packages/graphql/src/schema/make-augmented-schema.test.ts @@ -150,7 +150,6 @@ describe("makeAugmentedSchema", () => { `; const neoSchema = makeAugmentedSchema(typeDefs, { - validateResolvers: true, features: { filters: { String: { @@ -180,7 +179,6 @@ describe("makeAugmentedSchema", () => { `; const neoSchema = makeAugmentedSchema(typeDefs, { - validateResolvers: true, features: { filters: { ID: { @@ -210,7 +208,6 @@ describe("makeAugmentedSchema", () => { `; const neoSchema = makeAugmentedSchema(typeDefs, { - validateResolvers: true, features: { filters: { String: { @@ -247,7 +244,6 @@ describe("makeAugmentedSchema", () => { `; const neoSchema = makeAugmentedSchema(typeDefs, { - validateResolvers: true, features: { filters: { String: { diff --git a/packages/graphql/src/schema/make-augmented-schema.ts b/packages/graphql/src/schema/make-augmented-schema.ts index aea50c05fa..93d9f69d50 100644 --- a/packages/graphql/src/schema/make-augmented-schema.ts +++ b/packages/graphql/src/schema/make-augmented-schema.ts @@ -97,17 +97,15 @@ function makeAugmentedSchema( document: DocumentNode, { features, - validateResolvers, generateSubscriptions, userCustomResolvers, subgraph, }: { features?: Neo4jFeaturesSettings; - validateResolvers?: boolean; generateSubscriptions?: boolean; userCustomResolvers?: IResolvers | Array; subgraph?: Subgraph; - } = { validateResolvers: true } + } = {} ): { nodes: Node[]; relationships: Relationship[]; @@ -175,7 +173,7 @@ function makeAugmentedSchema( composer.addTypeDefs(print({ kind: Kind.DOCUMENT, definitions: extraDefinitions })); } - const getNodesResult = getNodes(definitionNodes, { callbacks, userCustomResolvers, validateResolvers }); + const getNodesResult = getNodes(definitionNodes, { callbacks, userCustomResolvers }); const { nodes, relationshipPropertyInterfaceNames, interfaceRelationshipNames, floatWhereInTypeDefs } = getNodesResult; @@ -229,7 +227,6 @@ function makeAugmentedSchema( unions: unionTypes, obj: relationship, callbacks, - validateResolvers, }); if (!pointInTypeDefs) { @@ -317,7 +314,6 @@ function makeAugmentedSchema( unions: unionTypes, obj: interfaceRelationship, callbacks, - validateResolvers, }); if (!pointInTypeDefs) { @@ -869,7 +865,6 @@ function makeAugmentedSchema( unions: unionTypes, objects: objectTypes, callbacks, - validateResolvers, }); const objectComposeFields = objectFieldsToComposeFields([ @@ -908,7 +903,6 @@ function makeAugmentedSchema( unions: unionTypes, objects: objectTypes, callbacks, - validateResolvers, }); const baseFields: BaseField[][] = Object.values(objectFields); diff --git a/packages/graphql/src/schema/parse/parse-fulltext-directive.test.ts b/packages/graphql/src/schema/parse/parse-fulltext-directive.test.ts index 993c72fcd7..8f967bfa52 100644 --- a/packages/graphql/src/schema/parse/parse-fulltext-directive.test.ts +++ b/packages/graphql/src/schema/parse/parse-fulltext-directive.test.ts @@ -44,7 +44,6 @@ describe("parseFulltextDirective", () => { scalars: [], unions: [], objects: [], - validateResolvers: true, }); expect(() => @@ -74,7 +73,6 @@ describe("parseFulltextDirective", () => { scalars: [], unions: [], objects: [], - validateResolvers: true, }); expect(() => @@ -112,7 +110,6 @@ describe("parseFulltextDirective", () => { scalars: [], unions: [], objects: [], - validateResolvers: true, }); const result = parseFulltextDirective({ diff --git a/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts b/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts index 350b231f01..d394e6310d 100644 --- a/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts +++ b/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts @@ -191,131 +191,27 @@ describe("Startup Validation", () => { await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); }); - test("should throw an error for missing custom resolvers by default", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: customResolverTypeDefs, - driver, - }); - - await expect(neoSchema.getSchema()).rejects.toThrow(missingCustomResolverError); - }); - - test("should not throw an error for missing custom resolvers when startupValidation is false", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: customResolverTypeDefs, - driver, - config: { - startupValidation: false, - }, - }); - - await expect(neoSchema.getSchema()).resolves.not.toThrow(); - }); - - test("should throw an error for missing custom resolvers when startupValidation is true", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: customResolverTypeDefs, - driver, - config: { - startupValidation: true, - }, - }); - - await expect(neoSchema.getSchema()).rejects.toThrow(missingCustomResolverError); - }); - - test("should not throw an error for missing custom resolvers when startupValidation.customResolver is false", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: customResolverTypeDefs, - driver, - config: { - startupValidation: { - typeDefs: false, - resolvers: false, - }, - }, - }); - - await expect(neoSchema.getSchema()).resolves.not.toThrow(); - }); - - test("should throw an error for missing custom resolvers when startupValidation.customResolver is true", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: customResolverTypeDefs, - driver, - config: { - startupValidation: { - typeDefs: true, - resolvers: true, - }, - }, - }); - - await expect(neoSchema.getSchema()).rejects.toThrow(missingCustomResolverError); - }); - - test("should throw an error for missing custom resolvers by default when startupValidation is an object", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: customResolverTypeDefs, - driver, - config: { - startupValidation: { - typeDefs: false, - }, - }, - }); - - await expect(neoSchema.getSchema()).rejects.toThrow(missingCustomResolverError); - }); + describe("@customResolver", () => { + let warn: jest.SpyInstance; - test("should throw an error for both type defs and custom resolvers by default", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidAndCustomResolverTypeDefs, - driver, - config: { - startupValidation: true, - }, + beforeEach(() => { + warn = jest.spyOn(console, "warn").mockImplementation(() => {}); }); - await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); - }); - - test("should throw an error for both type defs and custom resolvers when startupValidation is true", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidAndCustomResolverTypeDefs, - driver, - config: { - startupValidation: true, - }, + afterEach(() => { + warn.mockReset(); }); - await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); - }); - - test("should not throw an error for both type defs and custom resolvers when startupValidation is false", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidAndCustomResolverTypeDefs, - driver, - config: { - startupValidation: false, - }, - }); + test("should warn for missing custom resolvers", async () => { + const neoSchema = new Neo4jGraphQL({ + typeDefs: customResolverTypeDefs, + driver, + }); - await expect(neoSchema.getSchema()).resolves.not.toThrow(); - }); + await neoSchema.getSchema(); - test("should only throw an error for missing custom resolvers when startupValidation.typeDefs is false", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidAndCustomResolverTypeDefs, - driver, - config: { - startupValidation: { - typeDefs: false, - }, - }, + expect(warn).toHaveBeenCalledWith(missingCustomResolverError); }); - - await expect(neoSchema.getSchema()).rejects.toThrow(missingCustomResolverError); }); test("should only throw an error for invalid type defs when startupValidation.customResolvers is false", async () => { diff --git a/packages/graphql/tests/integration/directives/customResolver.int.test.ts b/packages/graphql/tests/integration/directives/customResolver.int.test.ts index b5e3502d0a..5dd6892521 100644 --- a/packages/graphql/tests/integration/directives/customResolver.int.test.ts +++ b/packages/graphql/tests/integration/directives/customResolver.int.test.ts @@ -280,13 +280,25 @@ describe("@customResolver directive", () => { }); }); }); + describe("Custom resolver checks", () => { + let warn: jest.SpyInstance; + + beforeEach(() => { + warn = jest.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + warn.mockReset(); + }); + test("Check throws error if customResolver is not provided", async () => { const neoSchema = new Neo4jGraphQL({ typeDefs }); - await expect(async () => { - await neoSchema.getSchema(); - }).rejects.toThrow(`Custom resolver for ${customResolverField} has not been provided`); + await neoSchema.getSchema(); + + expect(warn).toHaveBeenCalledWith(`Custom resolver for ${customResolverField} has not been provided`); }); + test("Check throws error if custom resolver defined for interface", async () => { const interfaceType = new UniqueType("UserInterface"); const typeDefs = ` @@ -309,9 +321,8 @@ describe("@customResolver directive", () => { }, }; const neoSchema = new Neo4jGraphQL({ typeDefs, resolvers }); - await expect(async () => { - await neoSchema.getSchema(); - }).rejects.toThrow(`Custom resolver for ${customResolverField} has not been provided`); + await neoSchema.getSchema(); + expect(warn).toHaveBeenCalledWith(`Custom resolver for ${customResolverField} has not been provided`); }); }); }); From dcf077de753cd40a9c54b66f62a71a9ccf8a00d2 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 19 Jul 2023 11:19:24 +0100 Subject: [PATCH 3/7] Unified validate option, removal of library config object --- packages/graphql/src/classes/Neo4jGraphQL.ts | 71 ++-------- packages/graphql/src/classes/index.ts | 2 +- .../graphql/src/schema/resolvers/wrapper.ts | 4 +- .../schema/validation/validate-document.ts | 23 ++-- .../validate-schema-customizations.ts | 15 +-- packages/graphql/src/types/index.ts | 13 -- .../startup-validation.int.test.ts | 126 ++++-------------- .../directives/customResolver.int.test.ts | 8 +- .../graphql/tests/tck/global-node.test.ts | 4 - 9 files changed, 52 insertions(+), 214 deletions(-) diff --git a/packages/graphql/src/classes/Neo4jGraphQL.ts b/packages/graphql/src/classes/Neo4jGraphQL.ts index 4dd88222db..803e4a988d 100644 --- a/packages/graphql/src/classes/Neo4jGraphQL.ts +++ b/packages/graphql/src/classes/Neo4jGraphQL.ts @@ -19,12 +19,7 @@ import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge"; import Debug from "debug"; -import type { - Neo4jFeaturesSettings, - StartupValidationConfig, - ContextFeatures, - Neo4jGraphQLSubscriptionsMechanism, -} from "../types"; +import type { Neo4jFeaturesSettings, ContextFeatures, Neo4jGraphQLSubscriptionsMechanism } from "../types"; import { makeAugmentedSchema } from "../schema"; import type Node from "./Node"; import type Relationship from "./Relationship"; @@ -55,34 +50,19 @@ import { makeDocumentToAugment } from "../schema/make-document-to-augment"; import { Neo4jGraphQLAuthorization } from "./authorization/Neo4jGraphQLAuthorization"; import { Neo4jGraphQLSubscriptionsDefaultMechanism } from "./Neo4jGraphQLSubscriptionsDefaultMechanism"; -export interface Neo4jGraphQLConfig { - startupValidation?: StartupValidationConfig; -} - -export type ValidationConfig = { - validateTypeDefs: boolean; - validateDuplicateRelationshipFields: boolean; -}; - export interface Neo4jGraphQLConstructor { typeDefs: TypeSource; resolvers?: IExecutableSchemaDefinition["resolvers"]; features?: Neo4jFeaturesSettings; - config?: Neo4jGraphQLConfig; driver?: Driver; debug?: boolean; + validate?: boolean; } -export const defaultValidationConfig: ValidationConfig = { - validateTypeDefs: true, - validateDuplicateRelationshipFields: true, -}; - class Neo4jGraphQL { private typeDefs: TypeSource; private resolvers?: IExecutableSchemaDefinition["resolvers"]; - private config: Neo4jGraphQLConfig; private driver?: Driver; private features: ContextFeatures; @@ -104,18 +84,19 @@ class Neo4jGraphQL { private authorization?: Neo4jGraphQLAuthorization; private debug?: boolean; + private validate: boolean; constructor(input: Neo4jGraphQLConstructor) { - const { config = {}, driver, features, typeDefs, resolvers, debug } = input; + const { driver, features, typeDefs, resolvers, debug, validate = true } = input; this.driver = driver; - this.config = config; this.features = this.parseNeo4jFeatures(features); this.typeDefs = typeDefs; this.resolvers = resolvers; this.debug = debug; + this.validate = validate; this.checkEnableDebug(); @@ -296,14 +277,8 @@ class Neo4jGraphQL { throw new Error("Schema Model is not defined"); } - const config = { - ...this.config, - callbacks: this.features?.populatedBy?.callbacks, - }; - const wrapResolverArgs: WrapResolverArguments = { driver: this.driver, - config, nodes: this.nodes, relationships: this.relationships, schemaModel: this.schemaModel, @@ -368,10 +343,8 @@ class Neo4jGraphQL { return new Promise((resolve) => { const initialDocument = this.getDocument(this.typeDefs); - const validationConfig = this.parseStartupValidationConfig(); - - if (validationConfig.validateTypeDefs) { - validateDocument({ document: initialDocument, validationConfig, features: this.features }); + if (this.validate) { + validateDocument({ document: initialDocument, features: this.features }); } const { document, typesExcludedFromGeneration } = makeDocumentToAugment(initialDocument); @@ -388,7 +361,7 @@ class Neo4jGraphQL { userCustomResolvers: this.resolvers, }); - if (validationConfig.validateTypeDefs) { + if (this.validate) { validateUserDefinition({ userDocument: document, augmentedDocument: typeDefs, jwt: jwt?.type }); } @@ -413,12 +386,9 @@ class Neo4jGraphQL { const { directives, types } = subgraph.getValidationDefinitions(); - const validationConfig = this.parseStartupValidationConfig(); - - if (validationConfig.validateTypeDefs) { + if (this.validate) { validateDocument({ document: initialDocument, - validationConfig, features: this.features, additionalDirectives: directives, additionalTypes: types, @@ -440,9 +410,7 @@ class Neo4jGraphQL { subgraph, }); - if (validationConfig.validateTypeDefs) { - // validateUserDefinition(document, typeDefs, directives, types); - // if (validateTypeDefs) { + if (this.validate) { validateUserDefinition({ userDocument: document, augmentedDocument: typeDefs, @@ -466,25 +434,6 @@ class Neo4jGraphQL { return this.composeSchema(schema); } - private parseStartupValidationConfig(): ValidationConfig { - const validationConfig: ValidationConfig = { ...defaultValidationConfig }; - - if (this.config?.startupValidation === false) { - return { - validateTypeDefs: false, - validateDuplicateRelationshipFields: false, - }; - } - - if (typeof this.config?.startupValidation === "object") { - if (this.config?.startupValidation.typeDefs === false) validationConfig.validateTypeDefs = false; - if (this.config?.startupValidation.noDuplicateRelationshipFields === false) - validationConfig.validateDuplicateRelationshipFields = false; - } - - return validationConfig; - } - private subscriptionMechanismSetup(): Promise { if (this.subscriptionInit) { return this.subscriptionInit; diff --git a/packages/graphql/src/classes/index.ts b/packages/graphql/src/classes/index.ts index 5f2055b038..75f7b0ec17 100644 --- a/packages/graphql/src/classes/index.ts +++ b/packages/graphql/src/classes/index.ts @@ -21,6 +21,6 @@ export * from "./Error"; export { default as Exclude, ExcludeConstructor } from "./Exclude"; export { GraphElement } from "./GraphElement"; export { Neo4jDatabaseInfo } from "./Neo4jDatabaseInfo"; -export { default as Neo4jGraphQL, Neo4jGraphQLConfig, Neo4jGraphQLConstructor } from "./Neo4jGraphQL"; +export { default as Neo4jGraphQL, Neo4jGraphQLConstructor } from "./Neo4jGraphQL"; export { default as Node, NodeConstructor } from "./Node"; export { default as Relationship } from "./Relationship"; diff --git a/packages/graphql/src/schema/resolvers/wrapper.ts b/packages/graphql/src/schema/resolvers/wrapper.ts index 713d373360..9f6fa979ed 100644 --- a/packages/graphql/src/schema/resolvers/wrapper.ts +++ b/packages/graphql/src/schema/resolvers/wrapper.ts @@ -22,7 +22,7 @@ import type { GraphQLResolveInfo } from "graphql"; import { print } from "graphql"; import type { Driver } from "neo4j-driver"; import { Neo4jError } from "neo4j-driver"; -import type { Neo4jGraphQLConfig, Node, Relationship } from "../../classes"; +import type { Node, Relationship } from "../../classes"; import type { Neo4jDatabaseInfo } from "../../classes/Neo4jDatabaseInfo"; import { getNeo4jDatabaseInfo } from "../../classes/Neo4jDatabaseInfo"; import { Executor } from "../../classes/Executor"; @@ -38,7 +38,6 @@ const debug = Debug(DEBUG_GRAPHQL); export type WrapResolverArguments = { driver?: Driver; - config: Neo4jGraphQLConfig; nodes: Node[]; relationships: Relationship[]; jwtPayloadFieldsMap?: Map; @@ -53,7 +52,6 @@ let neo4jDatabaseInfo: Neo4jDatabaseInfo; export const wrapResolver = ({ driver, - config, nodes, relationships, jwtPayloadFieldsMap, diff --git a/packages/graphql/src/schema/validation/validate-document.ts b/packages/graphql/src/schema/validation/validate-document.ts index af72ad599b..e285440bf1 100644 --- a/packages/graphql/src/schema/validation/validate-document.ts +++ b/packages/graphql/src/schema/validation/validate-document.ts @@ -41,8 +41,6 @@ import { CartesianPointDistance } from "../../graphql/input-objects/CartesianPoi import { RESERVED_TYPE_NAMES } from "../../constants"; import { isRootType } from "../../utils/is-root-type"; import { validateSchemaCustomizations } from "./validate-schema-customizations"; -import type { ValidationConfig } from "../../classes/Neo4jGraphQL"; -import { defaultValidationConfig } from "../../classes/Neo4jGraphQL"; import type { Neo4jFeaturesSettings } from "../../types"; function filterDocument(document: DocumentNode, features: Neo4jFeaturesSettings | undefined): DocumentNode { @@ -220,13 +218,11 @@ function filterDocument(document: DocumentNode, features: Neo4jFeaturesSettings function getBaseSchema({ document, - validateTypeDefs = true, features, additionalDirectives = [], additionalTypes = [], }: { document: DocumentNode; - validateTypeDefs: boolean; features: Neo4jFeaturesSettings | undefined; additionalDirectives: Array; additionalTypes: Array; @@ -248,18 +244,16 @@ function getBaseSchema({ ], }); - return extendSchema(schemaToExtend, doc, { assumeValid: !validateTypeDefs }); + return extendSchema(schemaToExtend, doc); } function validateDocument({ document, - validationConfig = defaultValidationConfig, features, additionalDirectives = [], additionalTypes = [], }: { document: DocumentNode; - validationConfig?: ValidationConfig; features: Neo4jFeaturesSettings | undefined; additionalDirectives?: Array; additionalTypes?: Array; @@ -267,18 +261,17 @@ function validateDocument({ const schema = getBaseSchema({ document, features, - validateTypeDefs: validationConfig.validateTypeDefs, additionalDirectives, additionalTypes, }); - if (validationConfig.validateTypeDefs) { - const errors = validateSchema(schema); - const filteredErrors = errors.filter((e) => e.message !== "Query root type must be provided."); - if (filteredErrors.length) { - throw new Error(filteredErrors.join("\n")); - } + + const errors = validateSchema(schema); + const filteredErrors = errors.filter((e) => e.message !== "Query root type must be provided."); + if (filteredErrors.length) { + throw new Error(filteredErrors.join("\n")); } - validateSchemaCustomizations({ document, schema, validationConfig }); + + validateSchemaCustomizations({ document, schema }); } export default validateDocument; diff --git a/packages/graphql/src/schema/validation/validate-schema-customizations.ts b/packages/graphql/src/schema/validation/validate-schema-customizations.ts index 1b9bc99eaa..45f26b1432 100644 --- a/packages/graphql/src/schema/validation/validate-schema-customizations.ts +++ b/packages/graphql/src/schema/validation/validate-schema-customizations.ts @@ -18,26 +18,15 @@ */ import type { DocumentNode, GraphQLSchema } from "graphql"; -import type { ValidationConfig } from "../../classes/Neo4jGraphQL"; import { getDefinitionNodes } from "../get-definition-nodes"; import { validateCustomResolverRequires } from "./validate-custom-resolver-requires"; import { validateDuplicateRelationshipFields } from "./validate-duplicate-relationship-fields"; -export function validateSchemaCustomizations({ - document, - schema, - validationConfig, -}: { - document: DocumentNode; - schema: GraphQLSchema; - validationConfig: ValidationConfig; -}) { +export function validateSchemaCustomizations({ document, schema }: { document: DocumentNode; schema: GraphQLSchema }) { const definitionNodes = getDefinitionNodes(document); for (const objectType of definitionNodes.objectTypes) { validateCustomResolverRequires(objectType, schema); - if (validationConfig.validateDuplicateRelationshipFields) { - validateDuplicateRelationshipFields(objectType); - } + validateDuplicateRelationshipFields(objectType); } } diff --git a/packages/graphql/src/types/index.ts b/packages/graphql/src/types/index.ts index 19f4ab884c..4662255760 100644 --- a/packages/graphql/src/types/index.ts +++ b/packages/graphql/src/types/index.ts @@ -301,19 +301,6 @@ export interface CypherQueryOptions { replan?: "default" | "force" | "skip"; } -/** The startup validation checks to run */ -export interface StartupValidationOptions { - typeDefs?: boolean; - resolvers?: boolean; - noDuplicateRelationshipFields?: boolean; -} - -/** - * Configure which startup validation checks should be run. - * Optionally, a boolean can be passed to toggle all these options. - */ -export type StartupValidationConfig = StartupValidationOptions | boolean; - /** Input field for graphql-compose */ export type InputField = { type: string; defaultValue?: string; directives?: Directive[] } | string; diff --git a/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts b/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts index d394e6310d..caf8bc0601 100644 --- a/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts +++ b/packages/graphql/tests/integration/config-options/startup-validation.int.test.ts @@ -107,9 +107,7 @@ describe("Startup Validation", () => { const neoSchema = new Neo4jGraphQL({ typeDefs: validTypeDefs, driver, - config: { - startupValidation: true, - }, + validate: true, }); await expect(neoSchema.getSchema()).resolves.not.toThrow(); @@ -124,68 +122,21 @@ describe("Startup Validation", () => { await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); }); - test("should not throw an error for invalid type defs when startupValidation is false", async () => { + test("should not throw an error for invalid type defs when validate is false", async () => { const neoSchema = new Neo4jGraphQL({ typeDefs: invalidTypeDefs, driver, - config: { - startupValidation: false, - }, + validate: false, }); await expect(neoSchema.getSchema()).resolves.not.toThrow(); }); - test("should throw an error for invalid type defs when startupValidation is true", async () => { + test("should throw an error for invalid type defs when validate is true", async () => { const neoSchema = new Neo4jGraphQL({ typeDefs: invalidTypeDefs, driver, - config: { - startupValidation: true, - }, - }); - - await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); - }); - - test("should not throw an error for invalid type defs when startupValidation.typeDefs is false", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidTypeDefs, - driver, - config: { - startupValidation: { - typeDefs: false, - }, - }, - }); - - await expect(neoSchema.getSchema()).resolves.not.toThrow(); - }); - - test("should throw an error for invalid type defs when startupValidation.typeDefs is true", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidTypeDefs, - driver, - config: { - startupValidation: { - typeDefs: true, - resolvers: false, - }, - }, - }); - - await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); - }); - - test("when startupValidation is an object, should throw an error for invalid type defs by default", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidTypeDefs, - driver, - config: { - startupValidation: { - resolvers: false, - }, - }, + validate: true, }); await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); @@ -212,76 +163,55 @@ describe("Startup Validation", () => { expect(warn).toHaveBeenCalledWith(missingCustomResolverError); }); - }); - test("should only throw an error for invalid type defs when startupValidation.customResolvers is false", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidAndCustomResolverTypeDefs, - driver, - config: { - startupValidation: { - resolvers: false, - }, - }, + test("should throw an error for invalid type defs when validate is true, and warn will not be reached", async () => { + const neoSchema = new Neo4jGraphQL({ + typeDefs: invalidAndCustomResolverTypeDefs, + driver, + validate: true, + }); + + await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); + expect(warn).not.toHaveBeenCalled(); }); - await expect(neoSchema.getSchema()).rejects.toThrow(invalidTypeDefsError); - }); + test("should throw no errors when validate is false, but warn for custom resolvers", async () => { + const neoSchema = new Neo4jGraphQL({ + typeDefs: invalidAndCustomResolverTypeDefs, + driver, + validate: false, + }); - test("should throw no errors when both startupValidation.customResolver and startupValidation.typeDefs are false", async () => { - const neoSchema = new Neo4jGraphQL({ - typeDefs: invalidAndCustomResolverTypeDefs, - driver, - config: { - startupValidation: { - resolvers: false, - typeDefs: false, - }, - }, + await expect(neoSchema.getSchema()).resolves.not.toThrow(); + expect(warn).toHaveBeenCalledWith(missingCustomResolverError); }); - - await expect(neoSchema.getSchema()).resolves.not.toThrow(); }); - test("should throw an error for duplicate relationship fields when startupValidation.noDuplicateRelationshipFields is true", async () => { + test("should throw an error for duplicate relationship fields when validate is true", async () => { const neoSchema = new Neo4jGraphQL({ typeDefs: invalidDuplicateRelationship, driver, - config: { - startupValidation: { - typeDefs: true, - resolvers: true, - noDuplicateRelationshipFields: true, - }, - }, + validate: true, }); await expect(neoSchema.getSchema()).rejects.toThrow(duplicateRelationshipFieldsError); }); - test("should not throw an error for duplicate relationship fields when startupValidation.noDuplicateRelationshipFields is false", async () => { + test("should not throw an error for duplicate relationship fields validate is false", async () => { const neoSchema = new Neo4jGraphQL({ typeDefs: invalidDuplicateRelationship, driver, - config: { - startupValidation: { - typeDefs: true, - resolvers: true, - noDuplicateRelationshipFields: false, - }, - }, + validate: false, }); await expect(neoSchema.getSchema()).resolves.not.toThrow(); }); - test("should throw no errors when startupValidation false", async () => { + test("should throw no errors when validate is false", async () => { const neoSchema = new Neo4jGraphQL({ typeDefs: invalidAll, driver, - config: { - startupValidation: false, - }, + validate: false, }); await expect(neoSchema.getSchema()).resolves.not.toThrow(); diff --git a/packages/graphql/tests/integration/directives/customResolver.int.test.ts b/packages/graphql/tests/integration/directives/customResolver.int.test.ts index 5dd6892521..fb777150c7 100644 --- a/packages/graphql/tests/integration/directives/customResolver.int.test.ts +++ b/packages/graphql/tests/integration/directives/customResolver.int.test.ts @@ -2495,7 +2495,7 @@ describe("Related Fields", () => { }); }); - test("should not throw an error for invalid type defs when startupValidation.typeDefs false", async () => { + test("should not throw an error for invalid type defs when validate is false", async () => { const typeDefs = gql` type ${Address} { houseNumber: Int! @cypher(statement: "RETURN 12 AS number", columnName: "number") @@ -2529,11 +2529,7 @@ describe("Related Fields", () => { const neoSchema = new Neo4jGraphQL({ typeDefs, resolvers, - config: { - startupValidation: { - typeDefs: false, - }, - }, + validate: false, }); const query = ` diff --git a/packages/graphql/tests/tck/global-node.test.ts b/packages/graphql/tests/tck/global-node.test.ts index fcc41bf089..a141c6f200 100644 --- a/packages/graphql/tests/tck/global-node.test.ts +++ b/packages/graphql/tests/tck/global-node.test.ts @@ -38,7 +38,6 @@ describe("Global nodes", () => { const neoSchema = new Neo4jGraphQL({ typeDefs, - config: {}, }); const query = gql` query Node($id: ID!) { @@ -78,7 +77,6 @@ describe("Global nodes", () => { `; const neoSchema = new Neo4jGraphQL({ typeDefs, - config: {}, }); const query = gql` query Node($id: ID!) { @@ -123,7 +121,6 @@ describe("Global nodes", () => { const neoSchema = new Neo4jGraphQL({ typeDefs, - config: {}, }); const query = gql` @@ -163,7 +160,6 @@ describe("Global nodes", () => { const neoSchema = new Neo4jGraphQL({ typeDefs, - config: {}, }); const query = gql` From 0980b5423426e870cf13afad428b4aa0f2589d78 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 19 Jul 2023 11:26:17 +0100 Subject: [PATCH 4/7] Docs updates --- docs/modules/ROOT/pages/custom-resolvers.adoc | 15 +- .../pages/migration/v4-migration/index.adoc | 82 +--------- .../reference/api-reference/neo4jgraphql.adoc | 152 ------------------ 3 files changed, 7 insertions(+), 242 deletions(-) diff --git a/docs/modules/ROOT/pages/custom-resolvers.adoc b/docs/modules/ROOT/pages/custom-resolvers.adoc index ad61ebbece..b6784d30cd 100644 --- a/docs/modules/ROOT/pages/custom-resolvers.adoc +++ b/docs/modules/ROOT/pages/custom-resolvers.adoc @@ -185,6 +185,7 @@ const resolvers = { ---- Instead, the following resolvers definition would be required: + [source, javascript, indent=0] ---- const resolvers = { @@ -196,16 +197,4 @@ const resolvers = { }; ---- -These checks may not always be required or desirable. If this is the case, they can be disabled using the `startupValidation` config option: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - startupValidation: { - resolvers: false - }, - }, -}) ----- +Mismatches between the resolver map and `@customResolver` directives will be logged to the console as a warning. diff --git a/docs/modules/ROOT/pages/migration/v4-migration/index.adoc b/docs/modules/ROOT/pages/migration/v4-migration/index.adoc index a7f01c67e2..20256c588f 100644 --- a/docs/modules/ROOT/pages/migration/v4-migration/index.adoc +++ b/docs/modules/ROOT/pages/migration/v4-migration/index.adoc @@ -206,28 +206,6 @@ new Neo4jGraphQL({ }); ---- -[[customResolver-checks]] -==== Checks for custom resolvers - -Previously, if no custom resolver was specified for a `@computed` field when creating an instance of Neo4jGraphQL, no errors would be thrown when generating the schema. -However, it is likely that the lack of a custom resolver would lead to errors at runtime. It is preferable to fail fast in this case as it is easier to debug and makes it less likely that bugs will make it into production. - -As a result, checks are now performed to ensure that every `@customResolver` field has a custom resolver provided. If not the library will throw an error during schema generation. - -These checks may not always be required or desirable. If this is the case, they can be disabled using the new xref::migration/v4-migration.adoc#startup-validation[`startupValidation`] config option: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - startupValidation: { - resolvers: false - }, - }, -}) ----- - ==== `requires` changes In version 4.0.0, it is now possible to require non-scalar fields. This means it is also possible to require fields on related type. @@ -645,17 +623,13 @@ type Person { In this example, there are multiple fields in the `Team` type which have the same `Person` type, the same `@relationship` type and ("PLAYS_IN") direction (IN). This is an issue when returning data from the database, as there would be no difference between `player1`, `player2` and `backupPlayers`. Selecting these fields would then return the same data. -To disable checks for duplicate relationship fields, the `noDuplicateRelationshipFields` config option should be used: +These checks can be disabled by disabling all validation in the library, however, this is not recommended unless in production with 100% confidence of type definitions input. [source, javascript, indent=0] ---- const neoSchema = new Neo4jGraphQL({ typeDefs, - config: { - startupValidation: { - noDuplicateRelationshipFields: false, - }, - }, + validate: false, }); ---- @@ -700,61 +674,15 @@ type Actor { [[startup-validation]] === Startup validation -In version 4.0.0, startup xref::migration/v4-migration.adoc#customResolver-checks[checks for custom resolvers], and checks for duplicate relationship fields have been added. As a result, a new configuration option has been added that can disable these checks. -This new option has been combined with the option to `skipValidateTypeDefs`. As a result, `skipValidateTypeDefs` will be removed and replaced by `startupValidation`. +The argument `config.skipValidateTypeDefs` has been moved to the top-level of the constructor input and renamed `validate`, which defaults to `true`. -To only disable strict type definition validation, the following config option should be used: +To disable type definition validation, the following config option should be used: [source, javascript, indent=0] ---- const neoSchema = new Neo4jGraphQL({ typeDefs, - config: { - startupValidation: { - typeDefs: false - }, - }, -}) ----- - -To only disable checks for custom resolvers, the following config option should be used: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - startupValidation: { - resolvers: false - }, - }, -}) ----- - -To only disable checks for duplicate relationship fields, the following config option should be used: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - startupValidation: { - noDuplicateRelationshipFields: false - }, - }, -}) ----- - - -To disable all startup checks, the following config option should be used: - -[source, javascript, indent=0] ----- -const neoSchema = new Neo4jGraphQL({ - typeDefs, - config: { - startupValidation: false, - }, + validate: false, }) ---- diff --git a/docs/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc b/docs/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc index 22f2587ba2..e87aa0f3b9 100644 --- a/docs/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc +++ b/docs/modules/ROOT/pages/reference/api-reference/neo4jgraphql.adoc @@ -29,162 +29,10 @@ Accepts all of the options from https://www.graphql-tools.com/docs/generate-sche Type: https://neo4j.com/docs/javascript-manual/current/[`Driver`] |An instance of a Neo4j driver. -|`config` + - + - Type: xref::reference/api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jgraphqlconfig[`Neo4jGraphQLConfig`] -|Additional Neo4j GraphQL configuration options. - |`features` + + Type: xref::reference/api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jfeaturessettings[`Neo4jFeaturesSettings`] |Could be used for configure/enable/disable specific features. - -|`plugins` + - + - Type: xref::reference/api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jgraphqlplugins[`Neo4jGraphQLPlugins`] -|Plugins for the Neo4j GraphQL Library. -|=== - -[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig]] -==== `Neo4jGraphQLConfig` - -|=== -|Name and Type |Description - -|`driverConfig` + - + - Type: xref::reference/api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jgraphqlconfig-driverconfig[`DriverConfig`] -|Additional driver configuration options. - -|`queryOptions` + - + - Type: xref::reference/api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jgraphqlconfig-cypherqueryoptions[`CypherQueryOptions`] -|Cypher query options, see xref::troubleshooting#troubleshooting-query-tuning[Query Tuning] for more information. - -|`startupValidation` + - + - Type: xref::reference/api-reference/neo4jgraphql.adoc#api-reference-neo4jgraphql-input-neo4jgraphqlconfig-StartupValidationOptions[`StartupValidationOptions`] or `boolean` -|Whether or not startup validation checks should be run. A boolean can be used to enable/disable all startup checks. Alternatively, a `StartupValidationOptions` object can be used for fine grain controls. If nothing is passed, all checks will be run. - -|=== - -[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-driverconfig]] -===== `DriverConfig` - -|=== -|Name and Type |Description - -|`database` + - + - Type: `string` -|The name of the database within the DBMS to connect to. - -|`bookmarks` + - + - Type: `string` or `Array` -|One or more bookmarks to use for the connection. -|=== - -[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-cypherqueryoptions]] -===== `CypherQueryOptions` - -All options are enum types imported from `@neo4j/graphql`, for example: - -[source, javascript, indent=0] ----- -const { CypherRuntime } = require("@neo4j/graphql"); ----- - -|=== -|Name and Type |Description - -|`runtime` + - + - Type: `CypherRuntime` -|Possible options: + - + - - `CypherRuntime.INTERPRETED` + - - `CypherRuntime.SLOTTED` + - - `CypherRuntime.PIPELINED` - -|`planner` + - + - Type: `CypherPlanner` -|Possible options: + - + - - `CypherPlanner.COST` + - - `CypherPlanner.IDP` + - - `CypherPlanner.DP` - -|`connectComponentsPlanner` + - + - Type: `CypherConnectComponentsPlanner` -|Possible options: + - + - - `CypherConnectComponentsPlanner.GREEDY` + - - `CypherConnectComponentsPlanner.IDP` - -|`updateStrategy` + - + - Type: `CypherUpdateStrategy` -|Possible options: + - + - - `CypherUpdateStrategy.DEFAULT` + - - `CypherUpdateStrategy.EAGER` - -|`expressionEngine` + - + - Type: `CypherExpressionEngine` -|Possible options: + - + - - `CypherExpressionEngine.DEFAULT` + - - `CypherExpressionEngine.INTERPRETED` + - - `CypherExpressionEngine.COMPILED` - -|`operatorEngine` + - + - Type: `CypherOperatorEngine` -|Possible options: + - + - - `CypherOperatorEngine.DEFAULT` + - - `CypherOperatorEngine.INTERPRETED` + - - `CypherOperatorEngine.COMPILED` - -|`interpretedPipesFallback` + - + - Type: `CypherInterpretedPipesFallback` -|Possible options: + - + - - `CypherInterpretedPipesFallback.DEFAULT` + - - `CypherInterpretedPipesFallback.DISABLED` + - - `CypherInterpretedPipesFallback.WHITELISTED_PLANS_ONLY` + - - `CypherInterpretedPipesFallback.ALL` - -|`replan` + - + - Type: `CypherReplanning` -|Possible options: + - + - - `CypherReplanning.DEFAULT` + - - `CypherReplanning.FORCE` + - - `CypherReplanning.SKIP` -|=== - -[[api-reference-neo4jgraphql-input-neo4jgraphqlconfig-StartupValidationOptions]] -===== `StartupValidationOptions` - -|=== -|Name and Type |Description - -|`typeDefs` + - + - Type: `boolean` -|Can be used to disable strict type definition validation. - -|`resolvers` + - + - Type: `boolean` -|Can be used to disable checks that expected custom resolvers have been provided. |=== [[api-reference-neo4jgraphql-input-neo4jfeaturessettings]] From 9176760de271b70a060037f94f6d65a892d504f3 Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 19 Jul 2023 11:27:02 +0100 Subject: [PATCH 5/7] Add changeset --- .changeset/smart-vans-greet.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/smart-vans-greet.md diff --git a/.changeset/smart-vans-greet.md b/.changeset/smart-vans-greet.md new file mode 100644 index 0000000000..81c29a353e --- /dev/null +++ b/.changeset/smart-vans-greet.md @@ -0,0 +1,6 @@ +--- +"@neo4j/graphql": major +"@neo4j/graphql-ogm": major +--- + +Validation of type definitions is now configured using the `validate` boolean option in the constructor, which defaults to `true`. From 4ea1b04bc7af7e20620b608288babf952031ecec Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 19 Jul 2023 13:32:08 +0100 Subject: [PATCH 6/7] Update docs --- docs/modules/ROOT/pages/custom-resolvers.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/custom-resolvers.adoc b/docs/modules/ROOT/pages/custom-resolvers.adoc index b6784d30cd..3b9c1a1ba1 100644 --- a/docs/modules/ROOT/pages/custom-resolvers.adoc +++ b/docs/modules/ROOT/pages/custom-resolvers.adoc @@ -171,7 +171,7 @@ type User implements UserInterface { } ---- -The following resolvers definition would be invalid: +The following resolvers definition would cause a warning to be logged: [source, javascript, indent=0] ---- @@ -184,7 +184,7 @@ const resolvers = { }; ---- -Instead, the following resolvers definition would be required: +The following resolvers definition would silence the warning: [source, javascript, indent=0] ---- @@ -197,4 +197,4 @@ const resolvers = { }; ---- -Mismatches between the resolver map and `@customResolver` directives will be logged to the console as a warning. +Mismatches between the resolver map and `@customResolver` directives will always be logged to the console as a warning. From c35e2d257f7565f7ecd3f4bbe492c5eeff0da69a Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Wed, 19 Jul 2023 14:02:01 +0100 Subject: [PATCH 7/7] Add a bit about `startupValidation` --- docs/modules/ROOT/pages/migration/v4-migration/index.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/modules/ROOT/pages/migration/v4-migration/index.adoc b/docs/modules/ROOT/pages/migration/v4-migration/index.adoc index 20256c588f..952d81b276 100644 --- a/docs/modules/ROOT/pages/migration/v4-migration/index.adoc +++ b/docs/modules/ROOT/pages/migration/v4-migration/index.adoc @@ -686,6 +686,9 @@ const neoSchema = new Neo4jGraphQL({ }) ---- +If you started using the `config.startupValidation` option, this has also been rolled into the same `validate` setting for simplicity. +The `resolvers` option of this is now just a warning, and `noDuplicateRelationshipFields` is now a mandatory check rolled into `validate`. + [[opt-in-aggregation]] === Opt-in Aggregation