From b76caefc40cd14ec2ee348725920e74d702f8c3b Mon Sep 17 00:00:00 2001 From: Darrell Warde Date: Fri, 30 Jul 2021 16:35:30 +0100 Subject: [PATCH] Filter out nested input types during validation --- .../validation/validate-document.test.ts | 79 +++++++++++++++++++ .../schema/validation/validate-document.ts | 75 +++++++++++------- 2 files changed, 124 insertions(+), 30 deletions(-) diff --git a/packages/graphql/src/schema/validation/validate-document.test.ts b/packages/graphql/src/schema/validation/validate-document.test.ts index eec828c2d0..419692e7e7 100644 --- a/packages/graphql/src/schema/validation/validate-document.test.ts +++ b/packages/graphql/src/schema/validation/validate-document.test.ts @@ -212,6 +212,85 @@ describe("validateDocument", () => { expect(res).toBeUndefined(); }); + test("should not throw error on use of internal input types within input types", () => { + const doc = parse(` + type Salary { + salaryId: ID! + amount: Float + currency: String + frequency: String + eligibleForBonus: Boolean + bonusPercentage: Float + salaryReviewDate: DateTime + pays_salary: EmploymentRecord @relationship(type: "PAYS_SALARY", direction: IN) + } + + type EmploymentRecord { + employmentRecordId: ID! + pays_salary: [Salary] @relationship(type: "PAYS_SALARY", direction: OUT) + } + + input EmpRecord { + employmentRecordId: ID! + salary: SalaryCreateInput + startDate: Date + endDate: Date + } + + type Mutation { + mergeSalaries(salaries: [SalaryCreateInput!]): [Salary] + @cypher( + statement: """ + UNWIND $salaries as salary + MERGE (s:Salary {salaryId: salary.salaryId}) + ON CREATE SET s.amount = salary.amount, + s.currency = salary.currency, + s.frequency = salary.frequency, + s.eligibleForBonus = salary.eligibleForBonus, + s.bonusPercentage = salary.bonusPercentage, + s.salaryReviewDate = salary.salaryReviewDate + ON MATCH SET s.amount = salary.amount, + s.currency = salary.currency, + s.frequency = salary.frequency, + s.eligibleForBonus = salary.eligibleForBonus, + s.bonusPercentage = salary.bonusPercentage, + s.salaryReviewDate = salary.salaryReviewDate + RETURN s + """ + ) + + mergeEmploymentRecords(employmentRecords: [EmpRecord]): [EmploymentRecord] + @cypher( + statement: """ + UNWIND $employmentRecords as employmentRecord + MERGE (er:EmploymentRecord { + employmentRecordId: employmentRecord.employmentRecordId + }) + MERGE (s:Salary {salaryId: employmentRecord.salary.salaryId}) + ON CREATE SET s.amount = employmentRecord.salary.amount, + s.currency = employmentRecord.salary.currency, + s.frequency = employmentRecord.salary.frequency, + s.eligibleForBonus = employmentRecord.salary.eligibleForBonus, + s.bonusPercentage = employmentRecord.salary.bonusPercentage, + s.salaryReviewDate = employmentRecord.salary.salaryReviewDate + ON MATCH SET s.amount = employmentRecord.salary.amount, + s.currency = employmentRecord.salary.currency, + s.frequency = employmentRecord.salary.frequency, + s.eligibleForBonus = employmentRecord.salary.eligibleForBonus, + s.bonusPercentage = employmentRecord.salary.bonusPercentage, + s.salaryReviewDate = employmentRecord.salary.salaryReviewDate + + MERGE (er)-[:PAYS_SALARY]->(s) + RETURN er + """ + ) + } + `); + + const res = validateDocument(doc); + expect(res).toBeUndefined(); + }); + describe("Github Issue 158", () => { test("should not throw error on validation of schema", () => { const doc = parse(` diff --git a/packages/graphql/src/schema/validation/validate-document.ts b/packages/graphql/src/schema/validation/validate-document.ts index 395bf747ad..0bd0fc927a 100644 --- a/packages/graphql/src/schema/validation/validate-document.ts +++ b/packages/graphql/src/schema/validation/validate-document.ts @@ -24,13 +24,14 @@ import { extendSchema, validateSchema, ObjectTypeDefinitionNode, + InputValueDefinitionNode, } from "graphql"; import * as scalars from "../scalars"; import * as enums from "./enums"; import * as directives from "./directives"; import * as point from "../point"; -function filterDocument(document: DocumentNode) { +function filterDocument(document: DocumentNode): DocumentNode { const nodeNames = document.definitions .filter((definition) => { if (definition.kind === "ObjectTypeDefinition") { @@ -42,40 +43,54 @@ function filterDocument(document: DocumentNode) { }) .map((definition) => (definition as ObjectTypeDefinitionNode).name.value); + const filterInputTypes = (fields: readonly InputValueDefinitionNode[] | undefined) => { + return fields?.filter((f) => { + const getArgumentType = (type) => { + if (["NonNullType", "ListType"].includes(type.kind)) { + return getArgumentType(type.type); + } + return type.name.value; + }; + const type = getArgumentType(f.type); + const match = /(?.+)(?:CreateInput|Sort|UpdateInput|Where)/gm.exec(type); + if (match?.groups?.nodeName) { + if (nodeNames.includes(match.groups.nodeName)) { + return false; + } + } + return true; + }); + }; + return { ...document, definitions: document.definitions.reduce((res: DefinitionNode[], def) => { - if (def.kind !== "ObjectTypeDefinition" && def.kind !== "InterfaceTypeDefinition") { - return [...res, def]; + if (def.kind === "InputObjectTypeDefinition") { + return [ + ...res, + { + ...def, + fields: filterInputTypes(def.fields), + }, + ]; + } + + if (def.kind === "ObjectTypeDefinition" || def.kind === "InterfaceTypeDefinition") { + return [ + ...res, + { + ...def, + directives: def.directives?.filter((x) => !["auth"].includes(x.name.value)), + fields: def.fields?.map((f) => ({ + ...f, + arguments: filterInputTypes(f.arguments), + directives: f.directives?.filter((x) => !["auth"].includes(x.name.value)), + })), + }, + ]; } - return [ - ...res, - { - ...def, - directives: def.directives?.filter((x) => !["auth"].includes(x.name.value)), - fields: def.fields?.map((f) => ({ - ...f, - arguments: f.arguments?.filter((argument) => { - const getArgumentType = (type) => { - if (["NonNullType", "ListType"].includes(type.kind)) { - return getArgumentType(type.type); - } - return type.name.value; - }; - const type = getArgumentType(argument.type); - const match = /(?.+)(?:CreateInput|Sort|UpdateInput|Where)/gm.exec(type); - if (match?.groups?.nodeName) { - if (nodeNames.includes(match.groups.nodeName)) { - return false; - } - } - return true; - }), - directives: f.directives?.filter((x) => !["auth"].includes(x.name.value)), - })), - }, - ]; + return [...res, def]; }, []), }; }