Skip to content

Commit

Permalink
feat(appsync): support Input Types for code-first approach (#10024)
Browse files Browse the repository at this point in the history
Support `Input Types` for code-first approach. `Input Types` are special types of Intermediate Types in CDK.

<details>
<summary>Desired GraphQL Input Type</summary>

```gql
input Review {
  stars: Int!
  commentary: String
}
```

</details>

The above GraphQL Input Type can be expressed in CDK as the following:

<details>
<summary>CDK Code</summary>

```ts
const review = new appsync.InputType('Review', {
  definition: {
    stars: GraphqlType.int({ isRequired: true }),
    commentary: GraphqlType.string(),
  },
}); 
api.addType(review);
```

</details>

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
BryanPan342 authored Aug 31, 2020
1 parent 34f40b9 commit 3f80ae6
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 40 deletions.
27 changes: 27 additions & 0 deletions packages/@aws-cdk/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ Types will be the meat of your GraphQL Schema as they are the types defined by y
Intermediate Types include:
- [**Interface Types**](#Interface-Types)
- [**Object Types**](#Object-Types)
- [**Input Types**](#Input-Types)

##### Interface Types

Expand Down Expand Up @@ -563,6 +564,32 @@ You can create Object Types in three ways:
```
> This method provides easy use and is ideal for smaller projects.

##### Input Types

**Input Types** are special types of Intermediate Types. They give users an
easy way to pass complex objects for top level Mutation and Queries.

```gql
input Review {
stars: Int!
commentary: String
}
```

The above GraphQL Input Type can be expressed in CDK as the following:

```ts
const review = new appsync.InputType('Review', {
definition: {
stars: GraphqlType.int({ isRequired: true }),
commentary: GraphqlType.string(),
},
});
api.addType(review);
```

To learn more about **Input Types**, read the docs [here](https://graphql.org/learn/schema/#input-types).

#### Query

Every schema requires a top level Query type. By default, the schema will look
Expand Down
29 changes: 26 additions & 3 deletions packages/@aws-cdk/aws-appsync/lib/schema-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,30 @@ export interface IField {
argsToString(): string;
}

/**
* The options to add a field to an Intermediate Type
*/
export interface AddFieldOptions {
/**
* The name of the field
*
* This option must be configured for Object, Interface,
* Input and Enum Types.
*
* @default - no fieldName
*/
readonly fieldName?: string;
/**
* The resolvable field to add
*
* This option must be configured for Object, Interface,
* Input and Union Types.
*
* @default - no IField
*/
readonly field?: IField;
}

/**
* Intermediate Types are types that includes a certain set of fields
* that define the entirety of your schema
Expand Down Expand Up @@ -123,10 +147,9 @@ export interface IIntermediateType {
/**
* Add a field to this Intermediate Type
*
* @param fieldName - The name of the field
* @param field - the resolvable field to add
* @param options - the options to add a field
*/
addField(fieldName: string, field: IField): void;
addField(options: AddFieldOptions): void;
}

/**
Expand Down
100 changes: 87 additions & 13 deletions packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { shapeAddition } from './private';
import { Resolver } from './resolver';
import { Directive, IField, IIntermediateType } from './schema-base';
import { Directive, IField, IIntermediateType, AddFieldOptions } from './schema-base';
import { BaseTypeOptions, GraphqlType, ResolvableFieldOptions } from './schema-field';

/**
Expand Down Expand Up @@ -69,13 +69,17 @@ export class InterfaceType implements IIntermediateType {
}

/**
* Add a field to this Object Type
* Add a field to this Interface Type.
*
* @param fieldName - The name of the field
* @param field - the field to add
* Interface Types must have both fieldName and field options.
*
* @param options the options to add a field
*/
public addField(fieldName: string, field: IField): void {
this.definition[fieldName] = field;
public addField(options: AddFieldOptions): void {
if (!options.fieldName || !options.field) {
throw new Error('Interface Types must have both fieldName and field options.');
}
this.definition[options.fieldName] = options.field;
}
}

Expand Down Expand Up @@ -144,15 +148,20 @@ export class ObjectType extends InterfaceType implements IIntermediateType {
});
}


/**
* Add a field to this Object Type
* Add a field to this Object Type.
*
* Object Types must have both fieldName and field options.
*
* @param fieldName - The name of the field
* @param field - the resolvable field to add
* @param options the options to add a field
*/
public addField(fieldName: string, field: IField): void {
this.generateResolver(fieldName, field.fieldOptions);
this.definition[fieldName] = field;
public addField(options: AddFieldOptions): void {
if (!options.fieldName || !options.field) {
throw new Error('Object Types must have both fieldName and field options.');
}
this.generateResolver(options.fieldName, options.field.fieldOptions);
this.definition[options.fieldName] = options.field;
}

/**
Expand Down Expand Up @@ -184,4 +193,69 @@ export class ObjectType extends InterfaceType implements IIntermediateType {
}));
}
}
}
}

/**
* Input Types are abstract types that define complex objects.
* They are used in arguments to represent
*
* @experimental
*/
export class InputType implements IIntermediateType {
/**
* the name of this type
*/
public readonly name: string;
/**
* the attributes of this type
*/
public readonly definition: { [key: string]: IField };

public constructor(name: string, props: IntermediateTypeProps) {
this.name = name;
this.definition = props.definition;
}

/**
* Create an GraphQL Type representing this Input Type
*
* @param options the options to configure this attribute
* - isList
* - isRequired
* - isRequiredList
*/
public attribute(options?: BaseTypeOptions): GraphqlType {
return GraphqlType.intermediate({
isList: options?.isList,
isRequired: options?.isRequired,
isRequiredList: options?.isRequiredList,
intermediateType: this,
});
}

/**
* Generate the string of this input type
*/
public toString(): string {
return shapeAddition({
prefix: 'input',
name: this.name,
fields: Object.keys(this.definition).map((key) =>
`${key}${this.definition[key].argsToString()}: ${this.definition[key].toString()}`),
});
}

/**
* Add a field to this Input Type.
*
* Input Types must have both fieldName and field options.
*
* @param options the options to add a field
*/
public addField(options: AddFieldOptions): void {
if (!options.fieldName || !options.field) {
throw new Error('Input Types must have both fieldName and field options.');
}
this.definition[options.fieldName] = options.field;
}
}
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-appsync/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export class Schema {
this.query = new ObjectType('Query', { definition: {} });
this.addType(this.query);
};
this.query.addField(fieldName, field);
this.query.addField({ fieldName, field });
return this.query;
}

Expand All @@ -142,7 +142,7 @@ export class Schema {
this.mutation = new ObjectType('Mutation', { definition: {} });
this.addType(this.mutation);
};
this.mutation.addField(fieldName, field);
this.mutation.addField({ fieldName, field });
return this.mutation;
}

Expand Down
16 changes: 8 additions & 8 deletions packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('code-first implementation through GraphQL Api functions`', () => {
},
});
api.addType(test);
test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });
const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n';

// THEN
Expand All @@ -51,7 +51,7 @@ describe('code-first implementation through GraphQL Api functions`', () => {
},
});
api.addType(test);
test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });
const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n';

// THEN
Expand Down Expand Up @@ -92,7 +92,7 @@ describe('code-first implementation through GraphQL Api functions`', () => {
},
}));

test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });
const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n';

// THEN
Expand Down Expand Up @@ -133,7 +133,7 @@ describe('code-first implementation through GraphQL Api functions`', () => {
},
}));

test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });
const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n';

// THEN
Expand Down Expand Up @@ -162,7 +162,7 @@ describe('code-first implementation through Schema functions`', () => {
},
});
schema.addType(test);
test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });

new appsync.GraphQLApi(stack, 'api', {
name: 'api',
Expand All @@ -188,7 +188,7 @@ describe('code-first implementation through Schema functions`', () => {
},
});
schema.addType(test);
test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });

new appsync.GraphQLApi(stack, 'api', {
name: 'api',
Expand Down Expand Up @@ -240,7 +240,7 @@ describe('code-first implementation through Schema functions`', () => {
},
}));

test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });
new appsync.GraphQLApi(stack, 'api', {
name: 'api',
schema,
Expand Down Expand Up @@ -289,7 +289,7 @@ describe('code-first implementation through Schema functions`', () => {
},
}));

test.addField('dupid', t.dup_id);
test.addField({ fieldName: 'dupid', field: t.dup_id });
new appsync.GraphQLApi(stack, 'api', {
name: 'api',
schema,
Expand Down
Loading

0 comments on commit 3f80ae6

Please sign in to comment.