Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(appsync): strongly type schema definition mode #9283

Merged
merged 15 commits into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import * as db from '@aws-cdk/aws-dynamodb';

const api = new appsync.GraphQLApi(stack, 'Api', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: join(__dirname, 'schema.graphql'),
authorizationConfig: {
defaultAuthorization: {
Expand Down
75 changes: 60 additions & 15 deletions packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,21 @@ export interface LogConfig {
readonly fieldLogLevel?: FieldLogLevel;
}

/**
* Enum containing the different modes of schema definition
*/
export enum SchemaDefinition {
/**
* Define schema through functions like addType, addQuery, etc.
*/
CODE = 'CODE',

/**
* Define schema in a file, i.e. schema.graphql
*/
FILE = 'FILE',
}

/**
* Properties for an AppSync GraphQL API
*/
Expand All @@ -227,11 +242,14 @@ export interface GraphQLApiProps {
readonly logConfig?: LogConfig;

/**
* GraphQL schema definition. You have to specify a definition or a file containing one.
* GraphQL schema definition. Specify how you want to define your schema.
*
* SchemaDefinition.CODE allows schema definition through CDK
* SchemaDefinition.FILE allows schema definition through schema.graphql file
*
* @default - Use schemaDefinitionFile
* @experimental
*/
readonly schemaDefinition?: string;
readonly schemaDefinition: SchemaDefinition;
/**
* File containing the GraphQL schema definition. You have to specify a definition or a file containing one.
*
Expand Down Expand Up @@ -332,6 +350,7 @@ export class GraphQLApi extends Construct {
return this._apiKey;
}

private schemaMode: SchemaDefinition;
private api: CfnGraphQLApi;
private _apiKey?: string;

Expand Down Expand Up @@ -389,6 +408,7 @@ export class GraphQLApi extends Construct {
this.arn = this.api.attrArn;
this.graphQlUrl = this.api.attrGraphQlUrl;
this.name = this.api.name;
this.schemaMode = props.schemaDefinition;

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

let definition;
if (props.schemaDefinition) {
definition = props.schemaDefinition;
} else if (props.schemaDefinitionFile) {
definition = readFileSync(props.schemaDefinitionFile).toString('UTF-8');
} else {
throw new Error('Missing Schema definition. Provide schemaDefinition or schemaDefinitionFile');
}
this.schema = new CfnGraphQLSchema(this, 'Schema', {
apiId: this.apiId,
definition,
});
this.schema = this.defineSchema(props.schemaDefinitionFile);
}

/**
Expand Down Expand Up @@ -666,4 +675,40 @@ export class GraphQLApi extends Construct {
const authModes = props.authorizationConfig?.additionalAuthorizationModes;
return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined;
}

/**
* Sets schema defintiion to input if schema mode is configured with SchemaDefinition.CODE
*
* @param definition string that is the graphql representation of schema
* @experimental temporary
*/
public updateDefinition (definition: string): void{
if ( this.schemaMode != SchemaDefinition.CODE ) {
throw new Error('API cannot add type because schema definition mode is not configured as CODE.');
}
this.schema.definition = definition;
}

/**
* Define schema based on props configuration
* @param file the file name/s3 location of Schema
*/
private defineSchema(file?: string): CfnGraphQLSchema {
let definition;

if ( this.schemaMode == SchemaDefinition.FILE && !file) {
throw new Error('schemaDefinitionFile must be configured if using FILE definition mode.');
} else if ( this.schemaMode == SchemaDefinition.FILE && file ) {
definition = readFileSync(file).toString('UTF-8');
} else if ( this.schemaMode == SchemaDefinition.CODE && !file ) {
definition = '';
} else if ( this.schemaMode == SchemaDefinition.CODE && file) {
throw new Error('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile');
}

return new CfnGraphQLSchema(this, 'Schema', {
apiId: this.apiId,
definition,
});
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appsync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@aws-cdk/aws-dynamodb": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.0.2"
},
Expand All @@ -84,6 +85,7 @@
"@aws-cdk/aws-dynamodb": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.0.2"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('AppSync Authorization Config', () => {
// WHEN
new appsync.GraphQLApi(stack, 'api', {
name: 'api',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
});

Expand All @@ -25,6 +26,7 @@ describe('AppSync Authorization Config', () => {
// WHEN
new appsync.GraphQLApi(stack, 'api', {
name: 'api',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
authorizationConfig: {
defaultAuthorization: {
Expand All @@ -47,6 +49,7 @@ describe('AppSync Authorization Config', () => {
// WHEN
new appsync.GraphQLApi(stack, 'api', {
name: 'api',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
authorizationConfig: {
defaultAuthorization: {
Expand All @@ -66,6 +69,7 @@ describe('AppSync Authorization Config', () => {
// WHEN
new appsync.GraphQLApi(stack, 'api', {
name: 'api',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
authorizationConfig: {
defaultAuthorization: {
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ beforeEach(() => {
});
api = new appsync.GraphQLApi(stack, 'API', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
authorizationConfig: {
defaultAuthorization: {
Expand Down
104 changes: 104 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { join } from 'path';
import '@aws-cdk/assert/jest';
import * as cdk from '@aws-cdk/core';
import * as appsync from '../lib';

// Schema Definitions
const type = 'type test {\n version: String!\n}\n\n';
const query = 'type Query {\n getTests: [ test! ]!\n}\n\n';
const mutation = 'type Mutation {\n addTest(version: String!): test\n}\n';

let stack: cdk.Stack;
beforeEach(() => {
// GIVEN
stack = new cdk.Stack();
});

describe('testing schema definition mode `code`', () => {

test('definition mode `code` produces empty schema definition', () => {
// WHEN
new appsync.GraphQLApi(stack, 'API', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.CODE,
});

//THEN
expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', {
Definition: '',
});
});

test('definition mode `code` generates correct schema with updateDefinition', () => {
// WHEN
const api = new appsync.GraphQLApi(stack, 'API', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.CODE,
});
api.updateDefinition(`${type}${query}${mutation}`);

//THEN
expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', {
Definition: `${type}${query}${mutation}`,
});
});

test('definition mode `code` errors when schemaDefinitionFile is configured', () => {
// WHEN
const when = () => {
new appsync.GraphQLApi(stack, 'API', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.CODE,
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
});
};

//THEN
expect(when).toThrowError('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile');
});

});

describe('testing schema definition mode `file`', () => {

test('definition mode `file` produces correct output', () => {
// WHEN
new appsync.GraphQLApi(stack, 'API', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
});

//THEN
expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', {
Definition: `${type}${query}${mutation}`,
});
});

test('definition mode `file` errors when calling updateDefiniton function', () => {
// WHEN
const api = new appsync.GraphQLApi(stack, 'API', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'),
});
const when = () => { api.updateDefinition('error'); };

//THEN
expect(when).toThrowError('API cannot add type because schema definition mode is not configured as CODE.');
});

test('definition mode `file` errors when schemaDefinitionFile is not configured', () => {
// WHEN
const when = () => {
new appsync.GraphQLApi(stack, 'API', {
name: 'demo',
schemaDefinition: appsync.SchemaDefinition.FILE,
});
};

//THEN
expect(when).toThrowError('schemaDefinitionFile must be configured if using FILE definition mode.');
});

});
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ test('should not throw an Error', () => {
const when = () => {
new appsync.GraphQLApi(stack, 'api', {
authorizationConfig: {},
schemaDefinition: appsync.SchemaDefinition.FILE,
name: 'api',
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
});
Expand All @@ -28,6 +29,7 @@ test('appsync should configure pipeline when pipelineConfig has contents', () =>
const api = new appsync.GraphQLApi(stack, 'api', {
authorizationConfig: {},
name: 'api',
schemaDefinition: appsync.SchemaDefinition.FILE,
schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'),
});

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

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

Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
UserPoolDefaultAction,
Values,
IamResource,
SchemaDefinition,
} from '../lib';

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

const api = new GraphQLApi(stack, 'Api', {
name: 'Integ_Test_IAM',
schemaDefinition: SchemaDefinition.FILE,
schemaDefinitionFile: join(__dirname, 'integ.graphql-iam.graphql'),
authorizationConfig: {
defaultAuthorization: {
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/integ.graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PrimaryKey,
UserPoolDefaultAction,
Values,
SchemaDefinition,
} from '../lib';

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

const api = new GraphQLApi(stack, 'Api', {
name: 'demoapi',
schemaDefinition: SchemaDefinition.FILE,
schemaDefinitionFile: join(__dirname, 'integ.graphql.graphql'),
authorizationConfig: {
defaultAuthorization: {
Expand Down