diff --git a/packages/amplify-graphql-default-value-transformer/package.json b/packages/amplify-graphql-default-value-transformer/package.json index 32bf38253d..9839b40476 100644 --- a/packages/amplify-graphql-default-value-transformer/package.json +++ b/packages/amplify-graphql-default-value-transformer/package.json @@ -75,7 +75,8 @@ "src/**/*.ts" ], "coveragePathIgnorePatterns": [ - "/__tests__/" + "/__tests__/", + "types.ts" ], "snapshotFormat": { "escapeString": true, diff --git a/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap b/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap index 320a27b4e9..d94e2aabac 100644 --- a/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap +++ b/packages/amplify-graphql-default-value-transformer/src/__tests__/__snapshots__/amplify-grapphql-default-value-transformer.test.ts.snap @@ -189,7 +189,7 @@ input ModelTestConditionInput { input CreateTestInput { id: ID - stringValue: String! + stringValue: String } input UpdateTestInput { diff --git a/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts b/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts index 344b350d5f..8e76bf9a07 100644 --- a/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts +++ b/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts @@ -217,6 +217,54 @@ describe('DefaultValueModelTransformer:', () => { ).toThrow('Default value "text" is not a valid AWSIPAddress.'); }); + it('throws if @default is used on an implicit primaryKey', () => { + const schema = ` + type Test @model { + id: ID! @default(value: "ID: 80361") + stringValue: String! + } + `; + + expect(() => + testTransform({ + schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer()], + }), + ).toThrow('The @default directive may not be applied to primaryKey fields.'); + }); + + it('throws if @default is used on an explicit primaryKey', () => { + const schema = ` + type Test @model { + id: ID! @default(value: "ID: 80361") @primaryKey + stringValue: String! + } + `; + + expect(() => + testTransform({ + schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], + }), + ).toThrow('The @default directive may not be applied to primaryKey fields.'); + }); + + it('throws if @default is used on a composite key member', () => { + const schema = ` + type Project @model { + projectId: ID! @primaryKey(sortKeyFields: ["name"]) + name: String! @default(value: "Mustapha Mond") + } + `; + + expect(() => + testTransform({ + schema, + transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], + }), + ).toThrow('The @default directive may not be applied to composite key member fields.'); + }); + it('should validate enum values', async () => { const inputSchema = ` type Post @model { diff --git a/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts b/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts index 15c76e22a5..01fd4b424f 100644 --- a/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts +++ b/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts @@ -20,6 +20,7 @@ import { FieldDefinitionNode, InterfaceTypeDefinitionNode, Kind, + ListValueNode, ObjectTypeDefinitionNode, StringValueNode, TypeNode, @@ -72,10 +73,34 @@ const validateDefaultValueType = (ctx: TransformerSchemaVisitStepContextProvider } }; +const validateNotPrimaryKey = (field: FieldDefinitionNode): void => { + const isPrimaryKeyField = + field.directives!.find((dir) => dir.name.value === 'primaryKey') || + (getBaseType(field.type) === 'ID' && field.type.kind === Kind.NON_NULL_TYPE && field.name.value === 'id'); + + if (isPrimaryKeyField) { + throw new InvalidDirectiveError('The @default directive may not be applied to primaryKey fields.'); + } +}; + +const validateNotCompositeKeyMember = (config: DefaultValueDirectiveConfiguration): void => { + const objectDirectives = config.object.fields?.flatMap((f) => f.directives); + const primaryKeyDirective = objectDirectives?.find((dir) => dir?.name.value === 'primaryKey'); + if (primaryKeyDirective) { + const sortKeyFields = primaryKeyDirective.arguments?.find((arg) => arg.name.value === 'sortKeyFields')?.value as ListValueNode; + const sortKeys = sortKeyFields?.values as StringValueNode[]; + if (sortKeys?.some((sortKey) => sortKey.value === config.field.name.value)) { + throw new InvalidDirectiveError('The @default directive may not be applied to composite key member fields.'); + } + } +}; + const validate = (ctx: TransformerSchemaVisitStepContextProvider, config: DefaultValueDirectiveConfiguration): void => { validateModelDirective(config); validateFieldType(ctx, config.field.type); validateDirectiveArguments(config.directive); + validateNotPrimaryKey(config.field); + validateNotCompositeKeyMember(config); // Validate the default values only for the DynamoDB datasource. // For SQL, the database determines and sets the default value. We will not validate the value in transformers. @@ -123,6 +148,7 @@ export class DefaultValueTransformer extends TransformerPluginBase { const input = InputObjectDefinitionWrapper.fromObject(name, config.object, ctx.inputDocument); const fieldWrapper = input.fields.find((f) => f.name === config.field.name.value); fieldWrapper?.makeNullable(); + ctx.output.updateInput(input.serialize()); } } };