diff --git a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts index a9797e8f0a3..df7a653bdb4 100644 --- a/packages/amplify-cli-core/src/feature-flags/featureFlags.ts +++ b/packages/amplify-cli-core/src/feature-flags/featureFlags.ts @@ -539,6 +539,12 @@ export class FeatureFlags { defaultValueForExistingProjects: false, defaultValueForNewProjects: true, }, + { + name: 'skipOverrideMutationInput', + type: 'boolean', + defaultValueForExistingProjects: false, + defaultValueForNewProjects: true, + } ]); this.registerFlag('frontend-ios', [ diff --git a/packages/graphql-key-transformer/src/KeyTransformer.ts b/packages/graphql-key-transformer/src/KeyTransformer.ts index a453b5a89c1..7fbd518c8d6 100644 --- a/packages/graphql-key-transformer/src/KeyTransformer.ts +++ b/packages/graphql-key-transformer/src/KeyTransformer.ts @@ -454,22 +454,35 @@ export class KeyTransformer extends Transformer { if (this.isPrimaryKey(directive)) { const directiveArgs: KeyArguments = getDirectiveArguments(directive); + // check @model mutation argument to update schema or not + let shouldMakeCreate = true; + let shouldMakeUpdate = true; + let shouldMakeDelete = true; + + if (ctx.featureFlags.getBoolean('skipOverrideMutationInputTypes', false)) { + const modelDirective = definition.directives.find(dir => dir.name.value === 'model'); + const modelDirectiveArgs = getDirectiveArguments(modelDirective); + // Figure out which mutations to make and if they have name overrides + shouldMakeCreate = !!modelDirectiveArgs?.mutations?.create; + shouldMakeUpdate = !!modelDirectiveArgs?.mutations?.update; + shouldMakeDelete = !!modelDirectiveArgs?.mutations?.delete; + } const hasIdField = definition.fields.find(f => f.name.value === 'id'); if (!hasIdField) { const createInput = ctx.getType( ModelResourceIDs.ModelCreateInputObjectName(definition.name.value), ) as InputObjectTypeDefinitionNode; - if (createInput) { + if (createInput && shouldMakeCreate) { ctx.putType(replaceCreateInput(definition, createInput, directiveArgs.fields)); } } const updateInput = ctx.getType(ModelResourceIDs.ModelUpdateInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode; - if (updateInput) { + if (updateInput && shouldMakeUpdate) { ctx.putType(replaceUpdateInput(definition, updateInput, directiveArgs.fields)); } const deleteInput = ctx.getType(ModelResourceIDs.ModelDeleteInputObjectName(definition.name.value)) as InputObjectTypeDefinitionNode; - if (deleteInput) { + if (deleteInput && shouldMakeDelete) { ctx.putType(replaceDeleteInput(definition, deleteInput, directiveArgs.fields)); } } diff --git a/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts b/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts index 22d8a7992b9..54e24fbde61 100644 --- a/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts +++ b/packages/graphql-key-transformer/src/__tests__/KeyTransformer.test.ts @@ -1,5 +1,5 @@ import { parse, InputObjectTypeDefinitionNode, DefinitionNode, DocumentNode, Kind } from 'graphql'; -import { GraphQLTransform, InvalidDirectiveError, SyncConfig, ConflictHandlerType } from 'graphql-transformer-core'; +import { GraphQLTransform, InvalidDirectiveError, SyncConfig, ConflictHandlerType, FeatureFlagProvider } from 'graphql-transformer-core'; import { KeyTransformer } from '../KeyTransformer'; import { DynamoDBModelTransformer } from 'graphql-dynamodb-transformer'; @@ -298,3 +298,99 @@ function getInputType(doc: DocumentNode, type: string): InputObjectTypeDefinitio | InputObjectTypeDefinitionNode | undefined; } + +describe('check schema input', () => { + let ff: FeatureFlagProvider; + beforeEach(() => { + ff = { + getBoolean: jest.fn().mockImplementation((name, defaultValue) => { + if (name === 'skipOverrideMutationInputTypes') { + return true; + } + }), + getNumber: jest.fn(), + getObject: jest.fn(), + getString: jest.fn(), + }; + }); + + it('@model mutation with user defined null args ', () => { + const validSchema = /* GraphQL */ ` + type Call + @model(queries: null, mutations: null) + @key(fields: ["receiverId", "senderId"]) + @key(name: "bySender", fields: ["senderId", "receiverId"]) { + senderId: ID! + receiverId: ID! + } + + type Mutation { + createCall(input: CreateCallInput!): Call + deleteCall(input: DeleteCallInput!): Call + } + + input CreateCallInput { + receiverId: ID! + } + + input DeleteCallInput { + receiverId: ID! + } + `; + const transformer = new GraphQLTransform({ + transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags: ff, + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + const schema = parse(out.schema); + + const DeleteCallInput: InputObjectTypeDefinitionNode = schema.definitions.find( + d => d.kind === 'InputObjectTypeDefinition' && d.name.value === 'DeleteCallInput', + ) as InputObjectTypeDefinitionNode | undefined; + expect(DeleteCallInput).toBeDefined(); + const receiverIdField = DeleteCallInput.fields.find(f => f.name.value === 'receiverId'); + expect(receiverIdField).toBeDefined(); + expect(receiverIdField.type.kind).toBe('NonNullType'); + const senderIdField = DeleteCallInput.fields.find(f => f.name.value === 'senderId'); + expect(senderIdField).toBeUndefined(); + }); + + it('@model mutation with user defined create args ', () => { + const validSchema = /* GraphQL */ ` + type Call + @model(queries: null, mutations: { delete: "testDelete" }) + @key(fields: ["receiverId", "senderId"]) + @key(name: "bySender", fields: ["senderId", "receiverId"]) { + senderId: ID! + receiverId: ID! + } + + input CreateCallInput { + receiverId: ID! + } + + input DeleteCallInput { + receiverId: ID! + } + `; + const transformer = new GraphQLTransform({ + transformers: [new DynamoDBModelTransformer(), new KeyTransformer()], + featureFlags: ff, + }); + const out = transformer.transform(validSchema); + expect(out).toBeDefined(); + const schema = parse(out.schema); + + const DeleteCallInput: InputObjectTypeDefinitionNode = schema.definitions.find( + d => d.kind === 'InputObjectTypeDefinition' && d.name.value === 'DeleteCallInput', + ) as InputObjectTypeDefinitionNode | undefined; + expect(DeleteCallInput).toBeDefined(); + const receiverIdField = DeleteCallInput.fields.find(f => f.name.value === 'receiverId'); + expect(receiverIdField).toBeDefined(); + expect(receiverIdField.type.kind).toBe('NonNullType'); + const senderIdField = DeleteCallInput.fields.find(f => f.name.value === 'senderId'); + expect(senderIdField).toBeDefined(); + expect(senderIdField.type.kind).toBe('NonNullType'); + }); +});