From 9498a1dc301bc3c4c1e8e3b1c1338bffa3002442 Mon Sep 17 00:00:00 2001 From: vctqs1 Date: Sat, 17 Aug 2024 13:30:50 +0700 Subject: [PATCH 1/6] able to duplicate field name in array --- .../UniqueInputFieldNamesRule-test.ts | 50 +++++++++++++++++++ .../rules/UniqueInputFieldNamesRule.ts | 44 ++++++++++++++-- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts index 33e4a2db01..919cbf2be2 100644 --- a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts +++ b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts @@ -53,6 +53,56 @@ describe('Validate: Unique input field names', () => { `); }); + it('allow and/or with duplicate fields in array', () => { + expectValid(` + { + field(arg: { and: [{ f: true }, { f: true }] }) + } + `); + expectValid(` + { + field(arg: { or: [{ f: true }, { f: {f1: true} }] }) + } + `); + expectValid(` + { + field(arg: { or: [{ f: true }, { f1: {f: true} }] }) + } + `); + }); + + it('duplicate input object fields in objects of array', () => { + expectErrors(` + { + field(arg: { or: [{ f: true, f: true }] }) + } + `).toDeepEqual([ + { + message: 'There can be only one input field named "f".', + locations: [ + { line: 3, column: 29 }, + { line: 3, column: 38 }, + ], + }, + ]); + }); + + it('nested input object fields in objects of array', () => { + expectErrors(` + { + field(arg: { or: [{f2: true}, { f1: {f2: "value", f2: "value" }}] }) + } + `).toDeepEqual([ + { + message: 'There can be only one input field named "f2".', + locations: [ + { line: 3, column: 46 }, + { line: 3, column: 59 }, + ], + }, + ]); + }); + it('duplicate input object fields', () => { expectErrors(` { diff --git a/src/validation/rules/UniqueInputFieldNamesRule.ts b/src/validation/rules/UniqueInputFieldNamesRule.ts index c1916a73b3..5b74a3381d 100644 --- a/src/validation/rules/UniqueInputFieldNamesRule.ts +++ b/src/validation/rules/UniqueInputFieldNamesRule.ts @@ -3,7 +3,7 @@ import type { ObjMap } from '../../jsutils/ObjMap'; import { GraphQLError } from '../../error/GraphQLError'; -import type { NameNode } from '../../language/ast'; +import type { NameNode, ObjectFieldNode } from '../../language/ast'; import type { ASTVisitor } from '../../language/visitor'; import type { ASTValidationContext } from '../ValidationContext'; @@ -22,6 +22,8 @@ export function UniqueInputFieldNamesRule( const knownNameStack: Array> = []; let knownNames: ObjMap = Object.create(null); + let knownNamesInList: (readonly ObjectFieldNode[])[] = Object.create([]); + return { ObjectValue: { enter() { @@ -34,18 +36,54 @@ export function UniqueInputFieldNamesRule( knownNames = prevKnownNames; }, }, + ListValue: { + enter(node) { + node.values.forEach((valueNode) => { + if(valueNode.kind === 'ObjectValue') { + knownNamesInList.push(valueNode.fields); + } + }); + }, + leave() { + knownNamesInList = Object.create([]); + }, + }, ObjectField(node) { const fieldName = node.name.value; + + let isError = false; if (knownNames[fieldName]) { + if (!knownNamesInList.length) { + isError = true; + } + else { + for (const fields of knownNamesInList) { + const nestedFields = fields.filter( + (field) => field.name.value === fieldName, + ); + + // expecting only one field with the same name, if there is more than one, report error. if there is no field with the same name, mean it is in the nested object instead list value, report error. + if (nestedFields.length !== 1) { + isError = true; + } + } + } + } + + + + if(isError) { context.reportError( new GraphQLError( `There can be only one input field named "${fieldName}".`, - { nodes: [knownNames[fieldName], node.name] }, + [knownNames[fieldName], node.name], ), ); - } else { + } + else { knownNames[fieldName] = node.name; } }, + }; } From 2eb4d832f734d974dc4dbd6cf1d943fbf5a253ad Mon Sep 17 00:00:00 2001 From: vctqs1 Date: Sat, 17 Aug 2024 13:47:18 +0700 Subject: [PATCH 2/6] able to duplicate field name in array --- src/validation/rules/UniqueInputFieldNamesRule.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/validation/rules/UniqueInputFieldNamesRule.ts b/src/validation/rules/UniqueInputFieldNamesRule.ts index 5b74a3381d..675d3bca59 100644 --- a/src/validation/rules/UniqueInputFieldNamesRule.ts +++ b/src/validation/rules/UniqueInputFieldNamesRule.ts @@ -22,7 +22,8 @@ export function UniqueInputFieldNamesRule( const knownNameStack: Array> = []; let knownNames: ObjMap = Object.create(null); - let knownNamesInList: (readonly ObjectFieldNode[])[] = Object.create([]); + let knownNamesInList: Array> = + Object.create([]); return { ObjectValue: { @@ -61,7 +62,7 @@ export function UniqueInputFieldNamesRule( const nestedFields = fields.filter( (field) => field.name.value === fieldName, ); - + // expecting only one field with the same name, if there is more than one, report error. if there is no field with the same name, mean it is in the nested object instead list value, report error. if (nestedFields.length !== 1) { isError = true; @@ -76,7 +77,7 @@ export function UniqueInputFieldNamesRule( context.reportError( new GraphQLError( `There can be only one input field named "${fieldName}".`, - [knownNames[fieldName], node.name], + { nodes: [knownNames[fieldName], node.name] }, ), ); } From f21ba929d3a1871608153996ef574f5bd5fb1a12 Mon Sep 17 00:00:00 2001 From: vctqs1 Date: Sat, 17 Aug 2024 14:06:52 +0700 Subject: [PATCH 3/6] perform check if current is not error --- src/validation/rules/UniqueInputFieldNamesRule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation/rules/UniqueInputFieldNamesRule.ts b/src/validation/rules/UniqueInputFieldNamesRule.ts index 675d3bca59..193c75f656 100644 --- a/src/validation/rules/UniqueInputFieldNamesRule.ts +++ b/src/validation/rules/UniqueInputFieldNamesRule.ts @@ -64,7 +64,7 @@ export function UniqueInputFieldNamesRule( ); // expecting only one field with the same name, if there is more than one, report error. if there is no field with the same name, mean it is in the nested object instead list value, report error. - if (nestedFields.length !== 1) { + if (!isError && nestedFields.length !== 1) { isError = true; } } From b1b5ca51f6fddad5329d60757a0d57e146b75b50 Mon Sep 17 00:00:00 2001 From: vctqs1 Date: Sun, 18 Aug 2024 11:55:35 +0700 Subject: [PATCH 4/6] using stack to hold the list value instead --- .../UniqueInputFieldNamesRule-test.ts | 33 +++++++++++++++---- .../rules/UniqueInputFieldNamesRule.ts | 31 +++++++++-------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts index 919cbf2be2..5134c096e7 100644 --- a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts +++ b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts @@ -69,19 +69,40 @@ describe('Validate: Unique input field names', () => { field(arg: { or: [{ f: true }, { f1: {f: true} }] }) } `); + expectValid(` + { + field(arg: { + or: [ + { field: true }, + { + deep1: { + deep2: { + and: [{ field: false }, { field: true }] + } + } + } + { + deep1: { + field: true + } + } + ] + }) + } + `); }); it('duplicate input object fields in objects of array', () => { expectErrors(` { - field(arg: { or: [{ f: true, f: true }] }) + field(arg: { or: [{ f: "value1", f: "value2" }] }) } `).toDeepEqual([ { message: 'There can be only one input field named "f".', locations: [ { line: 3, column: 29 }, - { line: 3, column: 38 }, + { line: 3, column: 42 }, ], }, ]); @@ -90,19 +111,19 @@ describe('Validate: Unique input field names', () => { it('nested input object fields in objects of array', () => { expectErrors(` { - field(arg: { or: [{f2: true}, { f1: {f2: "value", f2: "value" }}] }) + field(arg: { or: [ { f2: "value1" }, { f1: { f2: "value2", f2: "value3" } } ] }) } `).toDeepEqual([ { message: 'There can be only one input field named "f2".', locations: [ - { line: 3, column: 46 }, - { line: 3, column: 59 }, + { line: 3, column: 54 }, + { line: 3, column: 68 }, ], }, ]); }); - + it('duplicate input object fields', () => { expectErrors(` { diff --git a/src/validation/rules/UniqueInputFieldNamesRule.ts b/src/validation/rules/UniqueInputFieldNamesRule.ts index 193c75f656..0797020d7c 100644 --- a/src/validation/rules/UniqueInputFieldNamesRule.ts +++ b/src/validation/rules/UniqueInputFieldNamesRule.ts @@ -22,8 +22,8 @@ export function UniqueInputFieldNamesRule( const knownNameStack: Array> = []; let knownNames: ObjMap = Object.create(null); - let knownNamesInList: Array> = - Object.create([]); + const knownNamesInListStack: Array>> = + []; return { ObjectValue: { @@ -39,52 +39,55 @@ export function UniqueInputFieldNamesRule( }, ListValue: { enter(node) { + const knownNamesInList: Array> = + Object.create([]); node.values.forEach((valueNode) => { - if(valueNode.kind === 'ObjectValue') { + if (valueNode.kind === 'ObjectValue') { knownNamesInList.push(valueNode.fields); } }); + + knownNamesInListStack.push(knownNamesInList); }, leave() { - knownNamesInList = Object.create([]); + knownNamesInListStack.pop(); }, }, ObjectField(node) { const fieldName = node.name.value; - let isError = false; + if (knownNames[fieldName]) { + // get latest element in knownNamesInListStack + const knownNamesInList = + knownNamesInListStack[knownNamesInListStack.length - 1] || []; + if (!knownNamesInList.length) { isError = true; - } - else { + } else { for (const fields of knownNamesInList) { const nestedFields = fields.filter( (field) => field.name.value === fieldName, ); // expecting only one field with the same name, if there is more than one, report error. if there is no field with the same name, mean it is in the nested object instead list value, report error. - if (!isError && nestedFields.length !== 1) { + if (!isError && nestedFields.length !== 1) { isError = true; } } } } - - - if(isError) { + if (isError) { context.reportError( new GraphQLError( `There can be only one input field named "${fieldName}".`, { nodes: [knownNames[fieldName], node.name] }, ), ); - } - else { + } else { knownNames[fieldName] = node.name; } }, - }; } From 48dc656aa5bb893dede1b4d51b7134cd8901f8a7 Mon Sep 17 00:00:00 2001 From: vctqs1 Date: Sun, 18 Aug 2024 15:39:25 +0700 Subject: [PATCH 5/6] update test with value args --- src/validation/__tests__/UniqueInputFieldNamesRule-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts index 5134c096e7..5398d441a4 100644 --- a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts +++ b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts @@ -61,7 +61,7 @@ describe('Validate: Unique input field names', () => { `); expectValid(` { - field(arg: { or: [{ f: true }, { f: {f1: true} }] }) + field(arg: { or: [{ f: { f1: "value1" } }, { f: { f1: "value2" } }] }) } `); expectValid(` From 3706c2da39b3c1605a7cedd0c7e9947943f986f5 Mon Sep 17 00:00:00 2001 From: vctqs1 Date: Sun, 18 Aug 2024 15:40:12 +0700 Subject: [PATCH 6/6] update test with value args --- src/validation/__tests__/UniqueInputFieldNamesRule-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts index 5398d441a4..8b7d57e344 100644 --- a/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts +++ b/src/validation/__tests__/UniqueInputFieldNamesRule-test.ts @@ -56,7 +56,7 @@ describe('Validate: Unique input field names', () => { it('allow and/or with duplicate fields in array', () => { expectValid(` { - field(arg: { and: [{ f: true }, { f: true }] }) + field(arg: { and: [{ f: "value1" }, { f: "value2" }] }) } `); expectValid(`