diff --git a/.changeset/cyan-moose-hug.md b/.changeset/cyan-moose-hug.md new file mode 100644 index 000000000..604c8591b --- /dev/null +++ b/.changeset/cyan-moose-hug.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/data-schema': patch +--- + +Support nested custom type references and enums with required arrays in custom operations diff --git a/packages/data-schema/__tests__/ModelSchema.test.ts b/packages/data-schema/__tests__/ModelSchema.test.ts index 4e4a25c19..a0853b222 100644 --- a/packages/data-schema/__tests__/ModelSchema.test.ts +++ b/packages/data-schema/__tests__/ModelSchema.test.ts @@ -409,3 +409,61 @@ describe('custom operation argument inputs', () => { }); }) + +describe('custom mutations should handle array custom types', () => { + it('when the argument is a custom type with nested custom type containing an array field the input preserves nested structure', () => { + const fn1 = defineFunctionStub({}); + + const schema = a + .schema({ + tag: a.enum(['one', 'two']), + + innerType: a.customType({ + items: a.string().array(), + tags: a.ref('tag').array().required() + }), + + outerType: a.customType({ + inner: a.ref('innerType').required(), + }), + + testMutation: a + .mutation() + .arguments({ + testArgument: a.ref('outerType'), + }) + .returns(a.string()) + .handler(a.handler.function(fn1)) + }) + .authorization((allow) => allow.owner()); + + expect(schema.transform().schema).toMatchSnapshot(); + }); + + it('when the argument is a custom type with inline nested custom type containing required array the input preserves nested structure', () => { + const fn1 = defineFunctionStub({}); + + const schema = a + .schema({ + point: a.customType({ + x: a.float(), + y: a.float(), + }), + + testType: a.customType({ + points: a.ref('point').array().required() + }), + + testMutation: a + .mutation() + .arguments({ + testArgument: a.ref('testType').required(), + }) + .returns(a.string()) + .handler(a.handler.function(fn1)) + }) + .authorization((allow) => allow.owner()); + + expect(schema.transform().schema).toMatchSnapshot(); + }); +}) diff --git a/packages/data-schema/__tests__/__snapshots__/ModelSchema.test.ts.snap b/packages/data-schema/__tests__/__snapshots__/ModelSchema.test.ts.snap index 1e1d3821d..d3bcd6539 100644 --- a/packages/data-schema/__tests__/__snapshots__/ModelSchema.test.ts.snap +++ b/packages/data-schema/__tests__/__snapshots__/ModelSchema.test.ts.snap @@ -1,5 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`custom mutations should handle array custom types when the argument is a custom type with inline nested custom type containing required array the input preserves nested structure 1`] = ` +"type point +{ + x: Float + y: Float +} + +type testType +{ + points: [point]! +} + +input pointInput { + x: Float + y: Float +} + +input testTypeInput { + points: [pointInput]! +} + +type Mutation { + testMutation(testArgument: testTypeInput!): String @function(name: "FnTestMutation") @auth(rules: [{allow: owner, ownerField: "owner"}]) +}" +`; + +exports[`custom mutations should handle array custom types when the argument is a custom type with nested custom type containing an array field the input preserves nested structure 1`] = ` +"enum tag { + one + two +} + +type innerType +{ + items: [String] + tags: [tag]! +} + +type outerType +{ + inner: innerType! +} + +input innerTypeInput { + items: [String] + tags: [tag]! +} + +input outerTypeInput { + inner: innerTypeInput +} + +type Mutation { + testMutation(testArgument: outerTypeInput): String @function(name: "FnTestMutation") @auth(rules: [{allow: owner, ownerField: "owner"}]) +}" +`; + exports[`custom operation argument inputs when the argument is a custom type with a required field the input types field is required 1`] = ` "type testType { diff --git a/packages/data-schema/src/SchemaProcessor.ts b/packages/data-schema/src/SchemaProcessor.ts index 5e6f53131..0dac95c01 100644 --- a/packages/data-schema/src/SchemaProcessor.ts +++ b/packages/data-schema/src/SchemaProcessor.ts @@ -1352,6 +1352,30 @@ const mergeCustomTypeAuthRules = ( } }; +const constructNonScalarFieldDefinition = ( + nestedInputTypeName: string, + fieldDef: { data: { valueRequired?: boolean; array?: boolean; arrayRequired?: boolean } } +): { type: 'ref'; link: string; required?: boolean; array?: boolean; arrayRequired?: boolean } => { + const {valueRequired, array, arrayRequired} = fieldDef.data; + + const customTypeData: { type: 'ref'; link: string; required?: boolean; array?: boolean; arrayRequired?: boolean } = { + type: 'ref', + link: nestedInputTypeName + }; + + if (valueRequired) { + customTypeData.required = true; + } + if (array) { + customTypeData.array = true; + } + if (arrayRequired) { + customTypeData.arrayRequired = true; + } + + return customTypeData; +} + /** * Generates input types for custom operations in the schema. * @@ -1391,7 +1415,7 @@ function generateInputTypes( if (refType.type === 'CustomType') { const nestedInputTypeName = `${fieldDef.data.link}Input`; processedFields[fieldName] = { - data: { type: 'ref', link: nestedInputTypeName }, + data: constructNonScalarFieldDefinition(nestedInputTypeName, fieldDef), }; // Process the nested type if it hasn't been processed and isn't a circular reference @@ -1413,7 +1437,7 @@ function generateInputTypes( } } else if (refType.type === 'Enum') { processedFields[fieldName] = { - data: { type: 'ref', link: fieldDef.data.link }, + data: constructNonScalarFieldDefinition(fieldDef.data.link, fieldDef), }; } else { throw new Error(