Skip to content

Commit eb8bb28

Browse files
BryanPan342Curtis Eppel
authored and
Curtis Eppel
committed
refactor(appsync): strongly type schema definition mode (aws#9283)
**[ISSUE]** schema definition mode is not strongly typed. **[APPROACH]** Make input prop `schemaDefinition` take a enum that allows users to choose between 2 modes: `CODE` and `FILE`. **[NOTE]** This approach was taken in preparation for a code-first approach to schema generation with CDK. All of the functions tagged `@experimental` are subject to change. BREAKING CHANGE: **appsync** prop `schemaDefinition` no longer takes string, instead it is required to configure schema definition mode. - **appsync**: schemaDefinition takes param `SchemaDefinition.XXX` to declare how schema will be configured - **SchemaDefinition.CODE** allows schema definition through CDK - **SchemaDefinition.FILE** allows schema definition through schema.graphql file Fixes aws#9301 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 62fc56f commit eb8bb28

9 files changed

+180
-15
lines changed

packages/@aws-cdk/aws-appsync/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import * as db from '@aws-cdk/aws-dynamodb';
4747

4848
const api = new appsync.GraphQLApi(stack, 'Api', {
4949
name: 'demo',
50+
schemaDefinition: appsync.SchemaDefinition.FILE,
5051
schemaDefinitionFile: join(__dirname, 'schema.graphql'),
5152
authorizationConfig: {
5253
defaultAuthorization: {

packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts

+60-15
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,21 @@ export interface LogConfig {
202202
readonly fieldLogLevel?: FieldLogLevel;
203203
}
204204

205+
/**
206+
* Enum containing the different modes of schema definition
207+
*/
208+
export enum SchemaDefinition {
209+
/**
210+
* Define schema through functions like addType, addQuery, etc.
211+
*/
212+
CODE = 'CODE',
213+
214+
/**
215+
* Define schema in a file, i.e. schema.graphql
216+
*/
217+
FILE = 'FILE',
218+
}
219+
205220
/**
206221
* Properties for an AppSync GraphQL API
207222
*/
@@ -227,11 +242,14 @@ export interface GraphQLApiProps {
227242
readonly logConfig?: LogConfig;
228243

229244
/**
230-
* GraphQL schema definition. You have to specify a definition or a file containing one.
245+
* GraphQL schema definition. Specify how you want to define your schema.
246+
*
247+
* SchemaDefinition.CODE allows schema definition through CDK
248+
* SchemaDefinition.FILE allows schema definition through schema.graphql file
231249
*
232-
* @default - Use schemaDefinitionFile
250+
* @experimental
233251
*/
234-
readonly schemaDefinition?: string;
252+
readonly schemaDefinition: SchemaDefinition;
235253
/**
236254
* File containing the GraphQL schema definition. You have to specify a definition or a file containing one.
237255
*
@@ -332,6 +350,7 @@ export class GraphQLApi extends Construct {
332350
return this._apiKey;
333351
}
334352

353+
private schemaMode: SchemaDefinition;
335354
private api: CfnGraphQLApi;
336355
private _apiKey?: string;
337356

@@ -389,6 +408,7 @@ export class GraphQLApi extends Construct {
389408
this.arn = this.api.attrArn;
390409
this.graphQlUrl = this.api.attrGraphQlUrl;
391410
this.name = this.api.name;
411+
this.schemaMode = props.schemaDefinition;
392412

393413
if (
394414
defaultAuthorizationType === AuthorizationType.API_KEY ||
@@ -404,18 +424,7 @@ export class GraphQLApi extends Construct {
404424
this._apiKey = this.createAPIKey(apiKeyConfig);
405425
}
406426

407-
let definition;
408-
if (props.schemaDefinition) {
409-
definition = props.schemaDefinition;
410-
} else if (props.schemaDefinitionFile) {
411-
definition = readFileSync(props.schemaDefinitionFile).toString('UTF-8');
412-
} else {
413-
throw new Error('Missing Schema definition. Provide schemaDefinition or schemaDefinitionFile');
414-
}
415-
this.schema = new CfnGraphQLSchema(this, 'Schema', {
416-
apiId: this.apiId,
417-
definition,
418-
});
427+
this.schema = this.defineSchema(props.schemaDefinitionFile);
419428
}
420429

421430
/**
@@ -666,4 +675,40 @@ export class GraphQLApi extends Construct {
666675
const authModes = props.authorizationConfig?.additionalAuthorizationModes;
667676
return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined;
668677
}
678+
679+
/**
680+
* Sets schema defintiion to input if schema mode is configured with SchemaDefinition.CODE
681+
*
682+
* @param definition string that is the graphql representation of schema
683+
* @experimental temporary
684+
*/
685+
public updateDefinition (definition: string): void{
686+
if ( this.schemaMode != SchemaDefinition.CODE ) {
687+
throw new Error('API cannot add type because schema definition mode is not configured as CODE.');
688+
}
689+
this.schema.definition = definition;
690+
}
691+
692+
/**
693+
* Define schema based on props configuration
694+
* @param file the file name/s3 location of Schema
695+
*/
696+
private defineSchema(file?: string): CfnGraphQLSchema {
697+
let definition;
698+
699+
if ( this.schemaMode == SchemaDefinition.FILE && !file) {
700+
throw new Error('schemaDefinitionFile must be configured if using FILE definition mode.');
701+
} else if ( this.schemaMode == SchemaDefinition.FILE && file ) {
702+
definition = readFileSync(file).toString('UTF-8');
703+
} else if ( this.schemaMode == SchemaDefinition.CODE && !file ) {
704+
definition = '';
705+
} else if ( this.schemaMode == SchemaDefinition.CODE && file) {
706+
throw new Error('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile');
707+
}
708+
709+
return new CfnGraphQLSchema(this, 'Schema', {
710+
apiId: this.apiId,
711+
definition,
712+
});
713+
}
669714
}

packages/@aws-cdk/aws-appsync/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"@aws-cdk/aws-dynamodb": "0.0.0",
7676
"@aws-cdk/aws-iam": "0.0.0",
7777
"@aws-cdk/aws-lambda": "0.0.0",
78+
"@aws-cdk/aws-s3-assets": "0.0.0",
7879
"@aws-cdk/core": "0.0.0",
7980
"constructs": "^3.0.2"
8081
},
@@ -84,6 +85,7 @@
8485
"@aws-cdk/aws-dynamodb": "0.0.0",
8586
"@aws-cdk/aws-iam": "0.0.0",
8687
"@aws-cdk/aws-lambda": "0.0.0",
88+
"@aws-cdk/aws-s3-assets": "0.0.0",
8789
"@aws-cdk/core": "0.0.0",
8890
"constructs": "^3.0.2"
8991
},

packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('AppSync Authorization Config', () => {
1111
// WHEN
1212
new appsync.GraphQLApi(stack, 'api', {
1313
name: 'api',
14+
schemaDefinition: appsync.SchemaDefinition.FILE,
1415
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
1516
});
1617

@@ -25,6 +26,7 @@ describe('AppSync Authorization Config', () => {
2526
// WHEN
2627
new appsync.GraphQLApi(stack, 'api', {
2728
name: 'api',
29+
schemaDefinition: appsync.SchemaDefinition.FILE,
2830
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
2931
authorizationConfig: {
3032
defaultAuthorization: {
@@ -47,6 +49,7 @@ describe('AppSync Authorization Config', () => {
4749
// WHEN
4850
new appsync.GraphQLApi(stack, 'api', {
4951
name: 'api',
52+
schemaDefinition: appsync.SchemaDefinition.FILE,
5053
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
5154
authorizationConfig: {
5255
defaultAuthorization: {
@@ -66,6 +69,7 @@ describe('AppSync Authorization Config', () => {
6669
// WHEN
6770
new appsync.GraphQLApi(stack, 'api', {
6871
name: 'api',
72+
schemaDefinition: appsync.SchemaDefinition.FILE,
6973
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
7074
authorizationConfig: {
7175
defaultAuthorization: {

packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ beforeEach(() => {
1515
});
1616
api = new appsync.GraphQLApi(stack, 'API', {
1717
name: 'demo',
18+
schemaDefinition: appsync.SchemaDefinition.FILE,
1819
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
1920
authorizationConfig: {
2021
defaultAuthorization: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { join } from 'path';
2+
import '@aws-cdk/assert/jest';
3+
import * as cdk from '@aws-cdk/core';
4+
import * as appsync from '../lib';
5+
6+
// Schema Definitions
7+
const type = 'type test {\n version: String!\n}\n\n';
8+
const query = 'type Query {\n getTests: [ test! ]!\n}\n\n';
9+
const mutation = 'type Mutation {\n addTest(version: String!): test\n}\n';
10+
11+
let stack: cdk.Stack;
12+
beforeEach(() => {
13+
// GIVEN
14+
stack = new cdk.Stack();
15+
});
16+
17+
describe('testing schema definition mode `code`', () => {
18+
19+
test('definition mode `code` produces empty schema definition', () => {
20+
// WHEN
21+
new appsync.GraphQLApi(stack, 'API', {
22+
name: 'demo',
23+
schemaDefinition: appsync.SchemaDefinition.CODE,
24+
});
25+
26+
//THEN
27+
expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', {
28+
Definition: '',
29+
});
30+
});
31+
32+
test('definition mode `code` generates correct schema with updateDefinition', () => {
33+
// WHEN
34+
const api = new appsync.GraphQLApi(stack, 'API', {
35+
name: 'demo',
36+
schemaDefinition: appsync.SchemaDefinition.CODE,
37+
});
38+
api.updateDefinition(`${type}${query}${mutation}`);
39+
40+
//THEN
41+
expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', {
42+
Definition: `${type}${query}${mutation}`,
43+
});
44+
});
45+
46+
test('definition mode `code` errors when schemaDefinitionFile is configured', () => {
47+
// WHEN
48+
const when = () => {
49+
new appsync.GraphQLApi(stack, 'API', {
50+
name: 'demo',
51+
schemaDefinition: appsync.SchemaDefinition.CODE,
52+
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
53+
});
54+
};
55+
56+
//THEN
57+
expect(when).toThrowError('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile');
58+
});
59+
60+
});
61+
62+
describe('testing schema definition mode `file`', () => {
63+
64+
test('definition mode `file` produces correct output', () => {
65+
// WHEN
66+
new appsync.GraphQLApi(stack, 'API', {
67+
name: 'demo',
68+
schemaDefinition: appsync.SchemaDefinition.FILE,
69+
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
70+
});
71+
72+
//THEN
73+
expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', {
74+
Definition: `${type}${query}${mutation}`,
75+
});
76+
});
77+
78+
test('definition mode `file` errors when calling updateDefiniton function', () => {
79+
// WHEN
80+
const api = new appsync.GraphQLApi(stack, 'API', {
81+
name: 'demo',
82+
schemaDefinition: appsync.SchemaDefinition.FILE,
83+
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
84+
});
85+
const when = () => { api.updateDefinition('error'); };
86+
87+
//THEN
88+
expect(when).toThrowError('API cannot add type because schema definition mode is not configured as CODE.');
89+
});
90+
91+
test('definition mode `file` errors when schemaDefinitionFile is not configured', () => {
92+
// WHEN
93+
const when = () => {
94+
new appsync.GraphQLApi(stack, 'API', {
95+
name: 'demo',
96+
schemaDefinition: appsync.SchemaDefinition.FILE,
97+
});
98+
};
99+
100+
//THEN
101+
expect(when).toThrowError('schemaDefinitionFile must be configured if using FILE definition mode.');
102+
});
103+
104+
});

packages/@aws-cdk/aws-appsync/test/appsync.test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ test('should not throw an Error', () => {
1111
const when = () => {
1212
new appsync.GraphQLApi(stack, 'api', {
1313
authorizationConfig: {},
14+
schemaDefinition: appsync.SchemaDefinition.FILE,
1415
name: 'api',
1516
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
1617
});
@@ -28,6 +29,7 @@ test('appsync should configure pipeline when pipelineConfig has contents', () =>
2829
const api = new appsync.GraphQLApi(stack, 'api', {
2930
authorizationConfig: {},
3031
name: 'api',
32+
schemaDefinition: appsync.SchemaDefinition.FILE,
3133
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
3234
});
3335

@@ -53,6 +55,7 @@ test('appsync should configure resolver as unit when pipelineConfig is empty', (
5355
const api = new appsync.GraphQLApi(stack, 'api', {
5456
authorizationConfig: {},
5557
name: 'api',
58+
schemaDefinition: appsync.SchemaDefinition.FILE,
5659
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
5760
});
5861

@@ -76,6 +79,7 @@ test('appsync should configure resolver as unit when pipelineConfig is empty arr
7679
const api = new appsync.GraphQLApi(stack, 'api', {
7780
authorizationConfig: {},
7881
name: 'api',
82+
schemaDefinition: appsync.SchemaDefinition.FILE,
7983
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
8084
});
8185

packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
UserPoolDefaultAction,
1313
Values,
1414
IamResource,
15+
SchemaDefinition,
1516
} from '../lib';
1617

1718
/*
@@ -37,6 +38,7 @@ const userPool = new UserPool(stack, 'Pool', {
3738

3839
const api = new GraphQLApi(stack, 'Api', {
3940
name: 'Integ_Test_IAM',
41+
schemaDefinition: SchemaDefinition.FILE,
4042
schemaDefinitionFile: join(__dirname, 'integ.graphql-iam.graphql'),
4143
authorizationConfig: {
4244
defaultAuthorization: {

packages/@aws-cdk/aws-appsync/test/integ.graphql.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
PrimaryKey,
1111
UserPoolDefaultAction,
1212
Values,
13+
SchemaDefinition,
1314
} from '../lib';
1415

1516
/*
@@ -35,6 +36,7 @@ const userPool = new UserPool(stack, 'Pool', {
3536

3637
const api = new GraphQLApi(stack, 'Api', {
3738
name: 'demoapi',
39+
schemaDefinition: SchemaDefinition.FILE,
3840
schemaDefinitionFile: join(__dirname, 'integ.graphql.graphql'),
3941
authorizationConfig: {
4042
defaultAuthorization: {

0 commit comments

Comments
 (0)