Skip to content

[#4169] UniqueInputFieldNamesRule incorrectly identifies field names as duplicates within and/or array #4170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/validation/__tests__/UniqueInputFieldNamesRule-test.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was checking your tests and all of these also pass in current 16.x.x so I don't think this shows any issue in the current shape

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah, you're right. Sorry, I misread the format; the issue was with the {} inside the single big object (labeled in blue).
image

something like [{Id: {}, Id: {}}]

our tests cases was [{Id: {}}, {Id: {}}]

Copy link
Member

@JoviDeCroock JoviDeCroock Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but

  it('duplicate input object fields in objects of array', () => {
    expectErrors(`
      {
        field(arg: [{Id: {}, Id: {}}])
      }
    `).toDeepEqual([
      {
        message: 'There can be only one input field named "Id".',
        locations: [
          { line: 3, column: 22 },
          { line: 3, column: 30 },
        ],
      },
    ]);
  });

Correctly reports it as an error

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, based on that, I think the issue is with Salesforce rather than the current validation rule. Thank you for your response, thank you for spending time to help me validation again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, happy to help, if anything pops up feel free to reach out/create an issue so we can get to the bottom of this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, I'll the close PR right now. @JoviDeCroock

Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,77 @@ describe('Validate: Unique input field names', () => {
`);
});

it('allow and/or with duplicate fields in array', () => {
expectValid(`
{
field(arg: { and: [{ f: "value1" }, { f: "value2" }] })
}
`);
expectValid(`
{
field(arg: { or: [{ f: { f1: "value1" } }, { f: { f1: "value2" } }] })
}
`);
expectValid(`
{
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: "value1", f: "value2" }] })
}
`).toDeepEqual([
{
message: 'There can be only one input field named "f".',
locations: [
{ line: 3, column: 29 },
{ line: 3, column: 42 },
],
},
]);
});

it('nested input object fields in objects of array', () => {
expectErrors(`
{
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: 54 },
{ line: 3, column: 68 },
],
},
]);
});

it('duplicate input object fields', () => {
expectErrors(`
{
Expand Down
44 changes: 43 additions & 1 deletion src/validation/rules/UniqueInputFieldNamesRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -22,6 +22,9 @@ export function UniqueInputFieldNamesRule(
const knownNameStack: Array<ObjMap<NameNode>> = [];
let knownNames: ObjMap<NameNode> = Object.create(null);

const knownNamesInListStack: Array<Array<ReadonlyArray<ObjectFieldNode>>> =
[];

return {
ObjectValue: {
enter() {
Expand All @@ -34,9 +37,48 @@ export function UniqueInputFieldNamesRule(
knownNames = prevKnownNames;
},
},
ListValue: {
enter(node) {
const knownNamesInList: Array<ReadonlyArray<ObjectFieldNode>> =
Object.create([]);
node.values.forEach((valueNode) => {
if (valueNode.kind === 'ObjectValue') {
knownNamesInList.push(valueNode.fields);
}
});

knownNamesInListStack.push(knownNamesInList);
},
leave() {
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 {
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) {
isError = true;
}
}
}
}

if (isError) {
context.reportError(
new GraphQLError(
`There can be only one input field named "${fieldName}".`,
Expand Down
Loading