diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts index ceae35b364f..d3b6d5b5e37 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts +++ b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts @@ -108,6 +108,22 @@ test('throws if @index uses a sort key field that is a non-scalar', () => { }).toThrow(`The sort key of index 'wontwork' on type 'Test.email' cannot be a non-scalar.`); }); +test('throws if @index refers to itself', () => { + const schema = ` + type Test @model { + id: ID! @index(name: "wontwork", sortKeyFields: ["id"]) + email: String + }`; + + const transformer = new GraphQLTransform({ + transformers: [new ModelTransformer(), new IndexTransformer()], + }); + + expect(() => { + transformer.transform(schema); + }).toThrow(`@index field 'id' cannot reference itself.`); +}); + test('@index with multiple sort keys adds a query field and GSI correctly', () => { const inputSchema = ` type Test @model { diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts index c589957c632..7c65ad8630c 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts +++ b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts @@ -124,6 +124,22 @@ test('throws if @primaryKey uses a sort key field that is a non-scalar', () => { }).toThrow(`The primary key's sort key on type 'Test.email' cannot be a non-scalar.`); }); +test('throws if @primaryKey refers to itself', () => { + const schema = ` + type Test @model { + id: ID! @primaryKey(sortKeyFields: ["id"]) + email: String + }`; + + const transformer = new GraphQLTransform({ + transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], + }); + + expect(() => { + transformer.transform(schema); + }).toThrow(`@primaryKey field 'id' cannot reference itself.`); +}); + test('handles sortKeyFields being a string instead of an array', () => { const schema = ` type NonScalar { diff --git a/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts b/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts index 929f6a001a1..a2b16dadf2c 100644 --- a/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts +++ b/packages/amplify-graphql-index-transformer/src/graphql-index-transformer.ts @@ -17,6 +17,7 @@ import { isScalarOrEnum } from 'graphql-transformer-common'; import { appendSecondaryIndex, constructSyncVTL, updateResolversForIndex } from './resolvers'; import { addKeyConditionInputs, ensureQueryField, updateMutationConditionInput } from './schema'; import { IndexDirectiveConfiguration } from './types'; +import { validateNotSelfReferencing } from './utils'; const directiveName = 'index'; const directiveDefinition = ` @@ -87,6 +88,9 @@ export class IndexTransformer extends TransformerPluginBase { function validate(config: IndexDirectiveConfiguration, ctx: TransformerContextProvider): void { const { name, object, field, sortKeyFields } = config; + + validateNotSelfReferencing(config); + const modelDirective = object.directives!.find(directive => { return directive.name.value === 'model'; }); diff --git a/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts b/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts index e4e3aa2b76e..ed33c91ede8 100644 --- a/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts +++ b/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts @@ -24,6 +24,7 @@ import { updateMutationConditionInput, } from './schema'; import { PrimaryKeyDirectiveConfiguration } from './types'; +import { validateNotSelfReferencing } from './utils'; const directiveName = 'primaryKey'; const directiveDefinition = ` @@ -97,6 +98,9 @@ export class PrimaryKeyTransformer extends TransformerPluginBase { function validate(config: PrimaryKeyDirectiveConfiguration, ctx: TransformerContextProvider): void { const { object, field, sortKeyFields } = config; + + validateNotSelfReferencing(config); + const modelDirective = object.directives!.find(directive => { return directive.name.value === 'model'; }); diff --git a/packages/amplify-graphql-index-transformer/src/utils.ts b/packages/amplify-graphql-index-transformer/src/utils.ts index c75ef452845..13a474266c4 100644 --- a/packages/amplify-graphql-index-transformer/src/utils.ts +++ b/packages/amplify-graphql-index-transformer/src/utils.ts @@ -1,6 +1,7 @@ +import { InvalidDirectiveError } from '@aws-amplify/graphql-transformer-core'; import { TransformerContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { plurality } from 'graphql-transformer-common'; -import { PrimaryKeyDirectiveConfiguration } from './types'; +import { IndexDirectiveConfiguration, PrimaryKeyDirectiveConfiguration } from './types'; export function lookupResolverName(config: PrimaryKeyDirectiveConfiguration, ctx: TransformerContextProvider, op: string): string | null { const { object, modelDirective } = config; @@ -36,3 +37,14 @@ export function lookupResolverName(config: PrimaryKeyDirectiveConfiguration, ctx return resolverName; } + +export function validateNotSelfReferencing(config: IndexDirectiveConfiguration | PrimaryKeyDirectiveConfiguration) { + const { directive, field, sortKeyFields } = config; + const fieldName = field.name.value; + + for (const sortKeyField of sortKeyFields) { + if (sortKeyField === fieldName) { + throw new InvalidDirectiveError(`@${directive.name.value} field '${fieldName}' cannot reference itself.`); + } + } +}