diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index f086184760132..cb3b4d3708c5f 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -64,7 +64,7 @@ const demoTable = new db.Table(stack, 'DemoTable', { }, }); -const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos"', demoTable); +const demoDS = api.addDynamoDbDataSource('demoDataSource', demoTable); // Resolver for the Query "getDemos" that scans the DyanmoDb table and returns the entire list. demoDS.createResolver({ @@ -83,6 +83,24 @@ demoDS.createResolver({ }); ``` +## Imports + +Any GraphQL Api that has been created outside the stack can be imported from +another stack into your CDK app. Utilizing the `fromXxx` function, you have +the ability to add data sources and resolvers through a `IGraphQLApi` interface. + +```ts +const importedApi = appsync.GraphQLApi.fromGraphQLApiAttributes(stack, 'IApi', { + graphqlApiId: api.apiId, + graphqlArn: api.arn, +}); +importedApi.addDynamoDbDataSource('TableDataSource', table); +``` + +If you don't specify `graphqlArn` in `fromXxxAttributes`, CDK will autogenerate +the expected `arn` for the imported api, given the `apiId`. For creating data +sources and resolvers, an `apiId` is sufficient. + ## Permissions When using `AWS_IAM` as the authorization type for GraphQL API, an IAM Role diff --git a/packages/@aws-cdk/aws-appsync/lib/data-source.ts b/packages/@aws-cdk/aws-appsync/lib/data-source.ts index 0daf1f996a452..04c617b12e5d0 100644 --- a/packages/@aws-cdk/aws-appsync/lib/data-source.ts +++ b/packages/@aws-cdk/aws-appsync/lib/data-source.ts @@ -3,7 +3,7 @@ import { IGrantable, IPrincipal, IRole, Role, ServicePrincipal } from '@aws-cdk/ import { IFunction } from '@aws-cdk/aws-lambda'; import { Construct, IResolvable } from '@aws-cdk/core'; import { CfnDataSource } from './appsync.generated'; -import { GraphQLApi } from './graphqlapi'; +import { IGraphqlApi } from './graphqlapi-base'; import { BaseResolverProps, Resolver } from './resolver'; /** @@ -13,11 +13,13 @@ export interface BaseDataSourceProps { /** * The API to attach this data source to */ - readonly api: GraphQLApi; + readonly api: IGraphqlApi; /** * The name of the data source + * + * @default - id of data source */ - readonly name: string; + readonly name?: string; /** * the description of the data source * @@ -91,7 +93,7 @@ export abstract class BaseDataSource extends Construct { */ public readonly ds: CfnDataSource; - protected api: GraphQLApi; + protected api: IGraphqlApi; protected serviceRole?: IRole; constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) { @@ -100,15 +102,15 @@ export abstract class BaseDataSource extends Construct { if (extended.type !== 'NONE') { this.serviceRole = props.serviceRole || new Role(this, 'ServiceRole', { assumedBy: new ServicePrincipal('appsync') }); } - + const name = props.name ?? id; this.ds = new CfnDataSource(this, 'Resource', { apiId: props.api.apiId, - name: props.name, + name: name, description: props.description, serviceRoleArn: this.serviceRole?.roleArn, ...extended, }); - this.name = props.name; + this.name = name; this.api = props.api; } diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts new file mode 100644 index 0000000000000..2f357c142db91 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts @@ -0,0 +1,177 @@ +import { ITable } from '@aws-cdk/aws-dynamodb'; +import { IFunction } from '@aws-cdk/aws-lambda'; +import { CfnResource, IResource, Resource } from '@aws-cdk/core'; +import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; + +/** + * Optional configuration for data sources + */ +export interface DataSourceOptions { + /** + * The name of the data source, overrides the id given by cdk + * + * @default - generated by cdk given the id + */ + readonly name?: string; + + /** + * The description of the data source + * + * @default - No description + */ + readonly description?: string; +} + +/** + * Interface for GraphQL + */ +export interface IGraphqlApi extends IResource { + + /** + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' + * + * @attribute + */ + readonly apiId: string; + + /** + * the ARN of the API + * + * @attribute + */ + readonly arn: string; + + /** + * add a new dummy data source to this API. Useful for pipeline resolvers + * and for backend changes that don't require a data source. + * + * @param id The data source's id + * @param options The optional configuration for this data source + */ + addNoneDataSource(id: string, options?: DataSourceOptions): NoneDataSource; + + /** + * add a new DynamoDB data source to this API + * + * @param id The data source's id + * @param table The DynamoDB table backing this data source + * @param options The optional configuration for this data source + */ + addDynamoDbDataSource(id: string, table: ITable, options?: DataSourceOptions): DynamoDbDataSource; + + /** + * add a new http data source to this API + * + * @param id The data source's id + * @param endpoint The http endpoint + * @param options The optional configuration for this data source + */ + addHttpDataSource(id: string, endpoint: string, options?: DataSourceOptions): HttpDataSource; + + /** + * add a new Lambda data source to this API + * + * @param id The data source's id + * @param lambdaFunction The Lambda function to call to interact with this data source + * @param options The optional configuration for this data source + */ + addLambdaDataSource(id: string, lambdaFunction: IFunction, options?: DataSourceOptions): LambdaDataSource; + + /** + * Add schema dependency if not imported + * + * @param construct the dependee + */ + addSchemaDependency(construct: CfnResource): boolean; +} + +/** + * Base Class for GraphQL API + */ +export abstract class GraphqlApiBase extends Resource implements IGraphqlApi { + + /** + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' + */ + public abstract readonly apiId: string; + + /** + * the ARN of the API + */ + public abstract readonly arn: string; + + /** + * add a new dummy data source to this API. Useful for pipeline resolvers + * and for backend changes that don't require a data source. + * + * @param id The data source's id + * @param options The optional configuration for this data source + */ + public addNoneDataSource(id: string, options?: DataSourceOptions): NoneDataSource { + return new NoneDataSource(this, id, { + api: this, + name: options?.name, + description: options?.description, + }); + } + + /** + * add a new DynamoDB data source to this API + * + * @param id The data source's id + * @param table The DynamoDB table backing this data source + * @param options The optional configuration for this data source + */ + public addDynamoDbDataSource(id: string, table: ITable, options?: DataSourceOptions): DynamoDbDataSource { + return new DynamoDbDataSource(this, id, { + api: this, + table, + name: options?.name, + description: options?.description, + }); + } + + /** + * add a new http data source to this API + * + * @param id The data source's id + * @param endpoint The http endpoint + * @param options The optional configuration for this data source + */ + public addHttpDataSource(id: string, endpoint: string, options?: DataSourceOptions): HttpDataSource { + return new HttpDataSource(this, id, { + api: this, + endpoint, + name: options?.name, + description: options?.description, + }); + } + + /** + * add a new Lambda data source to this API + * + * @param id The data source's id + * @param lambdaFunction The Lambda function to call to interact with this data source + * @param options The optional configuration for this data source + */ + public addLambdaDataSource(id: string, lambdaFunction: IFunction, options?: DataSourceOptions): LambdaDataSource { + return new LambdaDataSource(this, id, { + api: this, + lambdaFunction, + name: options?.name, + description: options?.description, + }); + } + + /** + * Add schema dependency if not imported + * + * @param construct the dependee + */ + public addSchemaDependency(construct: CfnResource): boolean { + construct; + return false; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index c2c1e768e2d9b..af51819d16f42 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,11 +1,9 @@ import { readFileSync } from 'fs'; import { IUserPool } from '@aws-cdk/aws-cognito'; -import { ITable } from '@aws-cdk/aws-dynamodb'; -import { Grant, IGrantable, ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { IFunction } from '@aws-cdk/aws-lambda'; -import { Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; -import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; -import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; +import { ManagedPolicy, Role, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; +import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; +import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; +import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; /** * enum with all possible values for AppSync authorization type @@ -221,7 +219,6 @@ export enum SchemaDefinition { * Properties for an AppSync GraphQL API */ export interface GraphQLApiProps { - /** * the name of the GraphQL API */ @@ -324,31 +321,79 @@ export class IamResource { } } +/** + * Attributes for GraphQL imports + */ +export interface GraphqlApiAttributes { + /** + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' + */ + readonly graphqlApiId: string, + + /** + * the arn for the GraphQL Api + * @default - autogenerated arn + */ + readonly graphqlApiArn?: string, +} + /** * An AppSync GraphQL API + * + * @resource AWS::AppSync::GraphQLApi */ -export class GraphQLApi extends Construct { +export class GraphQLApi extends GraphqlApiBase { + /** + * Import a GraphQL API through this function + * + * @param scope scope + * @param id id + * @param attrs GraphQL API Attributes of an API + */ + public static fromGraphqlApiAttributes(scope: Construct, id: string, attrs: GraphqlApiAttributes): IGraphqlApi { + const arn = attrs.graphqlApiArn ?? Stack.of(scope).formatArn({ + service: 'appsync', + resource: `apis/${attrs.graphqlApiId}`, + }); + class Import extends GraphqlApiBase { + public readonly apiId = attrs.graphqlApiId; + public readonly arn = arn; + constructor (s: Construct, i: string){ + super(s, i); + } + } + return new Import(scope, id); + } /** - * the id of the GraphQL API + * an unique AWS AppSync GraphQL API identifier + * i.e. 'lxz775lwdrgcndgz3nurvac7oa' */ public readonly apiId: string; + /** * the ARN of the API */ public readonly arn: string; + /** * the URL of the endpoint created by AppSync + * + * @attribute */ public readonly graphQlUrl: string; + /** * the name of the API */ public readonly name: string; + /** * underlying CFN schema resource */ public readonly schema: CfnGraphQLSchema; + /** * the configured API key, if present */ @@ -434,72 +479,6 @@ export class GraphQLApi extends Construct { this.schema = this.defineSchema(props.schemaDefinitionFile); } - /** - * add a new dummy data source to this API - * @param name The name of the data source - * @param description The description of the data source - */ - public addNoneDataSource(name: string, description: string): NoneDataSource { - return new NoneDataSource(this, `${name}DS`, { - api: this, - description, - name, - }); - } - - /** - * add a new DynamoDB data source to this API - * @param name The name of the data source - * @param description The description of the data source - * @param table The DynamoDB table backing this data source [disable-awslint:ref-via-interface] - */ - public addDynamoDbDataSource( - name: string, - description: string, - table: ITable, - ): DynamoDbDataSource { - return new DynamoDbDataSource(this, `${name}DS`, { - api: this, - description, - name, - table, - }); - } - - /** - * add a new http data source to this API - * @param name The name of the data source - * @param description The description of the data source - * @param endpoint The http endpoint - */ - public addHttpDataSource(name: string, description: string, endpoint: string): HttpDataSource { - return new HttpDataSource(this, `${name}DS`, { - api: this, - description, - endpoint, - name, - }); - } - - /** - * add a new Lambda data source to this API - * @param name The name of the data source - * @param description The description of the data source - * @param lambdaFunction The Lambda function to call to interact with this data source - */ - public addLambdaDataSource( - name: string, - description: string, - lambdaFunction: IFunction, - ): LambdaDataSource { - return new LambdaDataSource(this, `${name}DS`, { - api: this, - description, - name, - lambdaFunction, - }); - } - /** * Adds an IAM policy statement associated with this GraphQLApi to an IAM * principal's policy. @@ -613,6 +592,16 @@ export class GraphQLApi extends Construct { } } + /** + * Add schema dependency to a given construct + * + * @param construct the dependee + */ + public addSchemaDependency(construct: CfnResource): boolean { + construct.addDependsOn(this.schema); + return true; + } + private formatOpenIdConnectConfig( config: OpenIdConnectConfig, ): CfnGraphQLApi.OpenIDConnectConfigProperty { diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index 852cbed1c54f6..e0736c1ecc397 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -5,3 +5,4 @@ export * from './data-source'; export * from './mapping-template'; export * from './resolver'; export * from './graphqlapi'; +export * from './graphqlapi-base'; diff --git a/packages/@aws-cdk/aws-appsync/lib/resolver.ts b/packages/@aws-cdk/aws-appsync/lib/resolver.ts index 5a226c448069a..205155cc791c8 100644 --- a/packages/@aws-cdk/aws-appsync/lib/resolver.ts +++ b/packages/@aws-cdk/aws-appsync/lib/resolver.ts @@ -1,7 +1,7 @@ import { Construct } from '@aws-cdk/core'; import { CfnResolver } from './appsync.generated'; import { BaseDataSource } from './data-source'; -import { GraphQLApi } from './graphqlapi'; +import { IGraphqlApi } from './graphqlapi-base'; import { MappingTemplate } from './mapping-template'; /** @@ -44,7 +44,7 @@ export interface ResolverProps extends BaseResolverProps { /** * The API this resolver is attached to */ - readonly api: GraphQLApi; + readonly api: IGraphqlApi; /** * The data source this resolver is using * @@ -79,7 +79,7 @@ export class Resolver extends Construct { requestMappingTemplate: props.requestMappingTemplate ? props.requestMappingTemplate.renderTemplate() : undefined, responseMappingTemplate: props.responseMappingTemplate ? props.responseMappingTemplate.renderTemplate() : undefined, }); - this.resolver.addDependsOn(props.api.schema); + props.api.addSchemaDependency(this.resolver); if (props.dataSource) { this.resolver.addDependsOn(props.dataSource.ds); } diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 07fa3726c0f9b..84bc2d3af6f7f 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -96,7 +96,9 @@ "exclude": [ "no-unused-type:@aws-cdk/aws-appsync.ApiKeyConfig", "no-unused-type:@aws-cdk/aws-appsync.UserPoolConfig", - "no-unused-type:@aws-cdk/aws-appsync.UserPoolDefaultAction" + "no-unused-type:@aws-cdk/aws-appsync.UserPoolDefaultAction", + "props-physical-name:@aws-cdk/aws-appsync.GraphQLApiProps", + "from-method:@aws-cdk/aws-appsync.GraphQLApi" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts index 39a09c1f1c955..9f47904770fcb 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert/jest'; -import * as cdk from '@aws-cdk/core'; import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; describe('AppSync Authorization Config', () => { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts index 37e1ecc0ea57b..a43e13afa9528 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts @@ -1,15 +1,93 @@ import '@aws-cdk/assert/jest'; -import { MappingTemplate, PrimaryKey, Values } from '../lib'; +import * as path from 'path'; +import * as db from '@aws-cdk/aws-dynamodb'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; function joined(str: string): string { return str.replace(/\s+/g, ''); } +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); +}); + +describe('DynamoDb Data Source configuration', () => { + // GIVEN + let table: db.Table; + beforeEach(() => { + table = new db.Table(stack, 'table', { + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, + }); + }); + + test('default configuration produces name `DynamoDbCDKDataSource`', () => { + // WHEN + api.addDynamoDbDataSource('ds', table); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addDynamoDbDataSource('ds', table, { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addDynamoDbDataSource('ds', table, { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple dynamo db data sources with no configuration', () => { + // WHEN + const when = () => { + api.addDynamoDbDataSource('ds', table); + api.addDynamoDbDataSource('ds', table); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); +}); + describe('DynamoDB Mapping Templates', () => { test('PutItem projecting all', () => { - const template = MappingTemplate.dynamoDbPutItem( - PrimaryKey.partition('id').is('id'), - Values.projecting(), + const template = appsync.MappingTemplate.dynamoDbPutItem( + appsync.PrimaryKey.partition('id').is('id'), + appsync.Values.projecting(), ); const rendered = joined(template.renderTemplate()); @@ -28,9 +106,9 @@ describe('DynamoDB Mapping Templates', () => { }); test('PutItem with invididual attributes', () => { - const template = MappingTemplate.dynamoDbPutItem( - PrimaryKey.partition('id').is('id'), - Values.attribute('val').is('ctx.args.val'), + const template = appsync.MappingTemplate.dynamoDbPutItem( + appsync.PrimaryKey.partition('id').is('id'), + appsync.Values.attribute('val').is('ctx.args.val'), ); const rendered = joined(template.renderTemplate()); @@ -50,9 +128,9 @@ describe('DynamoDB Mapping Templates', () => { }); test('PutItem with additional attributes', () => { - const template = MappingTemplate.dynamoDbPutItem( - PrimaryKey.partition('id').is('id'), - Values.projecting().attribute('val').is('ctx.args.val'), + const template = appsync.MappingTemplate.dynamoDbPutItem( + appsync.PrimaryKey.partition('id').is('id'), + appsync.Values.projecting().attribute('val').is('ctx.args.val'), ); const rendered = joined(template.renderTemplate()); @@ -70,4 +148,49 @@ describe('DynamoDB Mapping Templates', () => { }`), ); }); +}); + +describe('adding DynamoDb data source from imported api', () => { + // GIVEN + let table: db.Table; + beforeEach(() => { + table = new db.Table(stack, 'table', { + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, + }); + }); + + test('imported api can add DynamoDbDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addDynamoDbDataSource('ds', table); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add DynamoDbDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addDynamoDbDataSource('ds', table); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AMAZON_DYNAMODB', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts new file mode 100644 index 0000000000000..25961b87cb8d2 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts @@ -0,0 +1,106 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +let endpoint: string; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); + endpoint = 'aws.amazon.com'; +}); + +describe('Http Data Source configuration', () => { + + test('default configuration produces name `HttpCDKDataSource`', () => { + // WHEN + api.addHttpDataSource('ds', endpoint); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addHttpDataSource('ds', endpoint, { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addHttpDataSource('ds', endpoint, { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple http data sources with no configuration', () => { + // WHEN + const when = () => { + api.addHttpDataSource('ds', endpoint); + api.addHttpDataSource('ds', endpoint); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); +}); + +describe('adding http data source from imported api', () => { + test('imported api can add HttpDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addHttpDataSource('ds', endpoint); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add HttpDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addHttpDataSource('ds', endpoint); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'HTTP', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); +}); + + diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts new file mode 100644 index 0000000000000..b178fee2282cf --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts @@ -0,0 +1,121 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); +}); + +describe('Lambda Data Source configuration', () => { + // GIVEN + let func: lambda.Function; + beforeEach(() => { + func = new lambda.Function(stack, 'func', { + code: lambda.Code.fromAsset('test/verify'), + handler: 'iam-query.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + }); + + test('default configuration produces name `TableCDKDataSource`', () => { + // WHEN + api.addLambdaDataSource('ds', func); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addLambdaDataSource('ds', func, { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addLambdaDataSource('ds', func, { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple lambda data sources with no configuration', () => { + // WHEN + const when = () => { + api.addLambdaDataSource('ds', func); + api.addLambdaDataSource('ds', func); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); +}); + +describe('adding lambda data source from imported api',() => { + let func: lambda.Function; + beforeEach(() => { + func = new lambda.Function(stack, 'func', { + code: lambda.Code.fromAsset('test/verify'), + handler: 'iam-query.handler', + runtime: lambda.Runtime.NODEJS_12_X, + }); + }); + + test('imported api can add LambdaDbDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addLambdaDataSource('ds', func); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add LambdaDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addLambdaDataSource('ds', func); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'AWS_LAMBDA', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts new file mode 100644 index 0000000000000..13ab095ed0fee --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts @@ -0,0 +1,115 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'baseApi', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + }); +}); + +describe('None Data Source configuration', () => { + + test('default configuration produces name `NoneCDKDataSource`', () => { + // WHEN + api.addNoneDataSource('ds'); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addNoneDataSource('ds', { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addNoneDataSource('ds', { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple none data sources with no configuration', () => { + // WHEN + const when = () => { + api.addNoneDataSource('ds'); + api.addNoneDataSource('ds'); + }; + + // THEN + expect(when).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }); + + test('appsync errors when creating multiple none data sources with same name configuration', () => { + // WHEN + const when = () => { + api.addNoneDataSource('ds1', { name: 'custom' }); + api.addNoneDataSource('ds2', { name: 'custom' }); + }; + + // THEN + expect(when).not.toThrowError(); + }); +}); + +describe('adding none data source from imported api', () => { + test('imported api can add NoneDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addNoneDataSource('none'); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); + + test('imported api can add NoneDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addNoneDataSource('none'); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'NONE', + ApiId: { 'Fn::GetAtt': [ 'baseApiCDA4D43A', 'ApiId' ], + }, + }); + }); +}); + + diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.api-import.expected.json new file mode 100644 index 0000000000000..3a0b9a76b760c --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.expected.json @@ -0,0 +1,201 @@ +[ + { + "Resources": { + "baseApiCDA4D43A": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "baseApi" + } + }, + "baseApiDefaultAPIKeyApiKey4804ACE5": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "baseApiCDA4D43A", + "ApiId" + ] + }, + "Description": "Default API Key created by CDK" + } + }, + "baseApiSchemaB12C7BB0": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "baseApiCDA4D43A", + "ApiId" + ] + }, + "Definition": "type test {\n version: String!\n}\n\ntype Query {\n getTests: [ test! ]!\n}\n\ntype Mutation {\n addTest(version: String!): test\n}\n" + } + } + }, + "Outputs": { + "ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68": { + "Value": { + "Fn::GetAtt": [ + "baseApiCDA4D43A", + "ApiId" + ] + }, + "Export": { + "Name": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + } + } + } + }, + { + "Resources": { + "ApidsServiceRoleADC7D124": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ApidsServiceRoleDefaultPolicyE5E18D6D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TestTable5769773A", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ApidsServiceRoleDefaultPolicyE5E18D6D", + "Roles": [ + { + "Ref": "ApidsServiceRoleADC7D124" + } + ] + } + }, + "Apids0DB53FEA": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "Name": "ds", + "Type": "AMAZON_DYNAMODB", + "DynamoDBConfig": { + "AwsRegion": { + "Ref": "AWS::Region" + }, + "TableName": { + "Ref": "TestTable5769773A" + } + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "ApidsServiceRoleADC7D124", + "Arn" + ] + } + } + }, + "ApidsQuerygetTestsResolver952F49EE": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "FieldName": "getTests", + "TypeName": "Query", + "DataSourceName": "ds", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "Apids0DB53FEA" + ] + }, + "ApidsMutationaddTestResolverBCF0400B": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "FieldName": "addTest", + "TypeName": "Mutation", + "DataSourceName": "ds", + "Kind": "UNIT", + "RequestMappingTemplate": "\n #set($input = $ctx.args.test)\n \n {\n \"version\": \"2017-02-28\",\n \"operation\": \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }", + "ResponseMappingTemplate": "$util.toJson($ctx.result)" + }, + "DependsOn": [ + "Apids0DB53FEA" + ] + }, + "TestTable5769773A": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "api2noneC88DB89F": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::ImportValue": "baseStack:ExportsOutputFnGetAttbaseApiCDA4D43AApiId50287E68" + }, + "Name": "none", + "Type": "NONE" + } + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts new file mode 100644 index 0000000000000..74a8942246e51 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts @@ -0,0 +1,68 @@ +/// !cdk-integ * + +import * as path from 'path'; +import * as db from '@aws-cdk/aws-dynamodb'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +/* + * Creates an Appsync GraphQL API in a separate stack. + * Add dependencies to imported api. + * + * Stack verification steps: + * Install dependencies and deploy integration test. Check if data sources are + * connected to the graphQL Api + * + * -- cdk deploy --app 'node integ.api-import.js' stack -- start -- + * -- aws appsync list-graphql-apis -- obtain api id -- + * -- aws appsync list-data-sources --api-id [api_id] -- testDS/None -- + * -- cdk destroy --app 'node integ.api-import.js' stack baseStack -- clean -- + */ + +const app = new cdk.App(); +const baseStack = new cdk.Stack(app, 'baseStack'); + +const baseApi = new appsync.GraphQLApi(baseStack, 'baseApi', { + name: 'baseApi', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), +}); + +const stack = new cdk.Stack(app, 'stack'); +const api = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'Api', { + graphqlApiId: `${baseApi.apiId}`, +}); + +const testTable = new db.Table(stack, 'TestTable', { + billingMode: db.BillingMode.PAY_PER_REQUEST, + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, + }, + removalPolicy: cdk.RemovalPolicy.DESTROY, +}); + +const testDS = api.addDynamoDbDataSource('ds', testTable); + +testDS.createResolver({ + typeName: 'Query', + fieldName: 'getTests', + requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(), +}); + +testDS.createResolver({ + typeName: 'Mutation', + fieldName: 'addTest', + requestMappingTemplate: appsync.MappingTemplate.dynamoDbPutItem(appsync.PrimaryKey.partition('id').auto(), appsync.Values.projecting('test')), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(), +}); + +const api2 = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'api2', { + graphqlApiId: baseApi.apiId, + graphqlApiArn: baseApi.arn, +}); + +api2.addNoneDataSource('none'); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json index 1d90ae0c15d4b..eda4a662ffc58 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.expected.json @@ -63,7 +63,7 @@ "Definition": "type test @aws_iam {\n id: String!\n version: String!\n}\n\ntype Query {\n getTest(id: String!): test\n getTests: [ test! ]\n @aws_iam \n}\n\ninput TestInput {\n version: String!\n}\n\ntype Mutation {\n addTest(input: TestInput!): test\n @aws_iam\n}\n" } }, - "ApitestDataSourceDSServiceRoleE543E310": { + "ApidsServiceRoleADC7D124": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -80,7 +80,7 @@ } } }, - "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C": { + "ApidsServiceRoleDefaultPolicyE5E18D6D": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -114,15 +114,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C", + "PolicyName": "ApidsServiceRoleDefaultPolicyE5E18D6D", "Roles": [ { - "Ref": "ApitestDataSourceDSServiceRoleE543E310" + "Ref": "ApidsServiceRoleADC7D124" } ] } }, - "ApitestDataSourceDS776EA507": { + "Apids0DB53FEA": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -133,7 +133,6 @@ }, "Name": "testDataSource", "Type": "AMAZON_DYNAMODB", - "Description": "Table for Tests\"", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -144,13 +143,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApitestDataSourceDSServiceRoleE543E310", + "ApidsServiceRoleADC7D124", "Arn" ] } } }, - "ApitestDataSourceDSQuerygetTestResolver1A3C9201": { + "ApidsQuerygetTestResolverCCED7EC2": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -167,11 +166,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiSchema510EECD7", - "ApitestDataSourceDS776EA507" + "Apids0DB53FEA", + "ApiSchema510EECD7" ] }, - "ApitestDataSourceDSQuerygetTestsResolver61ED88B6": { + "ApidsQuerygetTestsResolver952F49EE": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -188,11 +187,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiSchema510EECD7", - "ApitestDataSourceDS776EA507" + "Apids0DB53FEA", + "ApiSchema510EECD7" ] }, - "ApitestDataSourceDSMutationaddTestResolver0D3A4591": { + "ApidsMutationaddTestResolverBCF0400B": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -209,8 +208,8 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiSchema510EECD7", - "ApitestDataSourceDS776EA507" + "Apids0DB53FEA", + "ApiSchema510EECD7" ] }, "TestTable5769773A": { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts index d55a63adcfb62..9613070f49dbc 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts @@ -65,7 +65,7 @@ const testTable = new Table(stack, 'TestTable', { removalPolicy: RemovalPolicy.DESTROY, }); -const testDS = api.addDynamoDbDataSource('testDataSource', 'Table for Tests"', testTable); +const testDS = api.addDynamoDbDataSource('ds', testTable, {name: 'testDataSource'}); testDS.createResolver({ typeName: 'Query', diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json index 80d03e19f7767..0406904978e66 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json @@ -75,7 +75,7 @@ "Definition": "type ServiceVersion @aws_api_key {\n version: String!\n}\n\ntype Customer @aws_api_key {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order @aws_api_key {\n customer: String!\n order: String!\n}\n\ntype Payment @aws_api_key {\n id: String!\n amount: String!\n}\n\ninput PaymentInput {\n amount: String!\n}\n\ntype Query @aws_api_key {\n getServiceVersion: ServiceVersion\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n getPayment(id: String): Payment\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation @aws_api_key {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n savePayment(payment: PaymentInput!): Payment\n doPostOnAws: String!\n}\n" } }, - "ApiNoneDSB4E6495F": { + "Apinone1F55F3F3": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -85,11 +85,10 @@ ] }, "Name": "None", - "Type": "NONE", - "Description": "Dummy data source" + "Type": "NONE" } }, - "ApiNoneDSQuerygetServiceVersionResolver4AF8FD42": { + "ApinoneQuerygetServiceVersionResolver336A3C2C": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -106,11 +105,11 @@ "ResponseMappingTemplate": "{\"version\":\"v1\"}" }, "DependsOn": [ - "ApiNoneDSB4E6495F", + "Apinone1F55F3F3", "ApiSchema510EECD7" ] }, - "ApiCustomerDSServiceRoleA929BCF7": { + "ApicustomerDsServiceRole76CAD539": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -127,7 +126,7 @@ } } }, - "ApiCustomerDSServiceRoleDefaultPolicy8C927D33": { + "ApicustomerDsServiceRoleDefaultPolicyF8E72AE7": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -161,15 +160,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApiCustomerDSServiceRoleDefaultPolicy8C927D33", + "PolicyName": "ApicustomerDsServiceRoleDefaultPolicyF8E72AE7", "Roles": [ { - "Ref": "ApiCustomerDSServiceRoleA929BCF7" + "Ref": "ApicustomerDsServiceRole76CAD539" } ] } }, - "ApiCustomerDS8C23CB2D": { + "ApicustomerDsFE73DAC5": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -180,7 +179,6 @@ }, "Name": "Customer", "Type": "AMAZON_DYNAMODB", - "Description": "The customer data source", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -191,13 +189,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApiCustomerDSServiceRoleA929BCF7", + "ApicustomerDsServiceRole76CAD539", "Arn" ] } } }, - "ApiCustomerDSQuerygetCustomersResolver0F8B3416": { + "ApicustomerDsQuerygetCustomersResolverA74C8A2E": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -214,11 +212,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSQuerygetCustomerResolver0DC795BE": { + "ApicustomerDsQuerygetCustomerResolver3649A130": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -235,11 +233,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationaddCustomerResolverC9041B1C": { + "ApicustomerDsMutationaddCustomerResolver4DE5B517": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -256,11 +254,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationsaveCustomerResolverB707057E": { + "ApicustomerDsMutationsaveCustomerResolver241DD231": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -277,11 +275,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationsaveCustomerWithFirstOrderResolver8B1277A8": { + "ApicustomerDsMutationsaveCustomerWithFirstOrderResolver7DE2CBC8": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -298,11 +296,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiCustomerDSMutationremoveCustomerResolverA0046C60": { + "ApicustomerDsMutationremoveCustomerResolverAD3AE7F5": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -319,11 +317,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiCustomerDS8C23CB2D", + "ApicustomerDsFE73DAC5", "ApiSchema510EECD7" ] }, - "ApiOrderDSServiceRole81A3E9E7": { + "ApiorderDsServiceRoleCC2040C0": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -340,7 +338,7 @@ } } }, - "ApiOrderDSServiceRoleDefaultPolicyDAB14B69": { + "ApiorderDsServiceRoleDefaultPolicy3315FCF4": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -374,15 +372,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApiOrderDSServiceRoleDefaultPolicyDAB14B69", + "PolicyName": "ApiorderDsServiceRoleDefaultPolicy3315FCF4", "Roles": [ { - "Ref": "ApiOrderDSServiceRole81A3E9E7" + "Ref": "ApiorderDsServiceRoleCC2040C0" } ] } }, - "ApiOrderDS4B3EEEBA": { + "ApiorderDsB50C8AAD": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -393,7 +391,6 @@ }, "Name": "Order", "Type": "AMAZON_DYNAMODB", - "Description": "The order data source", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -404,13 +401,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApiOrderDSServiceRole81A3E9E7", + "ApiorderDsServiceRoleCC2040C0", "Arn" ] } } }, - "ApiOrderDSQuerygetCustomerOrdersEqResolverFCC2003B": { + "ApiorderDsQuerygetCustomerOrdersEqResolverEF9D5350": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -427,11 +424,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersLtResolverE2C5E19E": { + "ApiorderDsQuerygetCustomerOrdersLtResolver909F3D8F": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -448,11 +445,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersLeResolver95C3D740": { + "ApiorderDsQuerygetCustomerOrdersLeResolverF230A8BE": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -469,11 +466,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersGtResolver1B99CF3D": { + "ApiorderDsQuerygetCustomerOrdersGtResolverF01F806B": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -490,11 +487,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersGeResolver5138C680": { + "ApiorderDsQuerygetCustomerOrdersGeResolver63CAD303": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -511,11 +508,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersFilterResolver4433E2B4": { + "ApiorderDsQuerygetCustomerOrdersFilterResolverCD2B8747": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -532,11 +529,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiOrderDSQuerygetCustomerOrdersBetweenResolver14F90CFD": { + "ApiorderDsQuerygetCustomerOrdersBetweenResolver7DEE368E": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -553,11 +550,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" }, "DependsOn": [ - "ApiOrderDS4B3EEEBA", + "ApiorderDsB50C8AAD", "ApiSchema510EECD7" ] }, - "ApiPaymentDSServiceRole7A857DD9": { + "ApipaymentDsServiceRole0DAC58D6": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -574,7 +571,7 @@ } } }, - "ApiPaymentDSServiceRoleDefaultPolicy1BE875C5": { + "ApipaymentDsServiceRoleDefaultPolicy528E42B0": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { @@ -622,15 +619,15 @@ ], "Version": "2012-10-17" }, - "PolicyName": "ApiPaymentDSServiceRoleDefaultPolicy1BE875C5", + "PolicyName": "ApipaymentDsServiceRoleDefaultPolicy528E42B0", "Roles": [ { - "Ref": "ApiPaymentDSServiceRole7A857DD9" + "Ref": "ApipaymentDsServiceRole0DAC58D6" } ] } }, - "ApiPaymentDS69022256": { + "ApipaymentDs95C7AC36": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -641,7 +638,6 @@ }, "Name": "Payment", "Type": "AMAZON_DYNAMODB", - "Description": "The payment data source", "DynamoDBConfig": { "AwsRegion": { "Ref": "AWS::Region" @@ -650,13 +646,13 @@ }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApiPaymentDSServiceRole7A857DD9", + "ApipaymentDsServiceRole0DAC58D6", "Arn" ] } } }, - "ApiPaymentDSQuerygetPaymentResolver25686F48": { + "ApipaymentDsQuerygetPaymentResolverD172BFC9": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -673,11 +669,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiPaymentDS69022256", + "ApipaymentDs95C7AC36", "ApiSchema510EECD7" ] }, - "ApiPaymentDSMutationsavePaymentResolver08FBC62D": { + "ApipaymentDsMutationsavePaymentResolverE09FE5BB": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -694,11 +690,11 @@ "ResponseMappingTemplate": "$util.toJson($ctx.result)" }, "DependsOn": [ - "ApiPaymentDS69022256", + "ApipaymentDs95C7AC36", "ApiSchema510EECD7" ] }, - "ApihttpDSServiceRole8B5C9457": { + "ApidsServiceRoleADC7D124": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -715,7 +711,7 @@ } } }, - "ApihttpDS91F12990": { + "Apids0DB53FEA": { "Type": "AWS::AppSync::DataSource", "Properties": { "ApiId": { @@ -726,19 +722,18 @@ }, "Name": "http", "Type": "HTTP", - "Description": "The http data source", "HttpConfig": { "Endpoint": "https://aws.amazon.com/" }, "ServiceRoleArn": { "Fn::GetAtt": [ - "ApihttpDSServiceRole8B5C9457", + "ApidsServiceRoleADC7D124", "Arn" ] } } }, - "ApihttpDSMutationdoPostOnAwsResolverA9027953": { + "ApidsMutationdoPostOnAwsResolver9583D8A3": { "Type": "AWS::AppSync::Resolver", "Properties": { "ApiId": { @@ -755,7 +750,7 @@ "ResponseMappingTemplate": "\n ## Raise a GraphQL field error in case of a datasource invocation error\n #if($ctx.error)\n $util.error($ctx.error.message, $ctx.error.type)\n #end\n ## if the response status code is not 200, then return an error. Else return the body **\n #if($ctx.result.statusCode == 200)\n ## If response is 200, return the body.\n $ctx.result.body\n #else\n ## If response is not 200, append the response to error block.\n $utils.appendError($ctx.result.body, \"$ctx.result.statusCode\")\n #end\n " }, "DependsOn": [ - "ApihttpDS91F12990", + "Apids0DB53FEA", "ApiSchema510EECD7" ] }, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 3b8564dba6865..add2e19cb8b68 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -54,7 +54,7 @@ const api = new GraphQLApi(stack, 'Api', { }, }); -const noneDS = api.addNoneDataSource('None', 'Dummy data source'); +const noneDS = api.addNoneDataSource('none', {name: 'None'}); noneDS.createResolver({ typeName: 'Query', @@ -99,9 +99,9 @@ new Table(stack, 'PaymentTable', { const paymentTable = Table.fromTableName(stack, 'ImportedPaymentTable', 'PaymentTable'); -const customerDS = api.addDynamoDbDataSource('Customer', 'The customer data source', customerTable); -const orderDS = api.addDynamoDbDataSource('Order', 'The order data source', orderTable); -const paymentDS = api.addDynamoDbDataSource('Payment', 'The payment data source', paymentTable); +const customerDS = api.addDynamoDbDataSource('customerDs', customerTable, {name: 'Customer'}); +const orderDS = api.addDynamoDbDataSource('orderDs', orderTable, {name: 'Order'}); +const paymentDS = api.addDynamoDbDataSource('paymentDs', paymentTable, {name: 'Payment'}); customerDS.createResolver({ typeName: 'Query', @@ -189,7 +189,7 @@ paymentDS.createResolver({ responseMappingTemplate: MappingTemplate.dynamoDbResultItem(), }); -const httpDS = api.addHttpDataSource('http', 'The http data source', 'https://aws.amazon.com/'); +const httpDS = api.addHttpDataSource('ds', 'https://aws.amazon.com/', {name: 'http'}); httpDS.createResolver({ typeName: 'Mutation',