From 46df9f06c9d1af435fda0586739bcdb7e80312c5 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 22 Jul 2020 14:31:28 -0700 Subject: [PATCH 01/13] complete SchemaBaseType --- .../aws-appsync/lib/schema-util-abstract.ts | 179 ++++++++++++++++++ .../@aws-cdk/aws-appsync/lib/schema-util.ts | 142 ++++++++++++++ packages/@aws-cdk/aws-appsync/lib/schema.ts | 14 ++ 3 files changed, 335 insertions(+) create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-util.ts create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts b/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts new file mode 100644 index 0000000000000..37b8c9324da85 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts @@ -0,0 +1,179 @@ +import { AuthorizationType } from "./graphqlapi"; + +/** + * Enum containing the SchemaTypes that can be used to define ObjectTypes + */ +export enum SchemaType { + /** + * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. + * + * Often used as a key for a cache and not intended to be human-readable. + */ + id = 'ID', + /** + * `String` scalar type is a free-form human-readable text. + */ + string = 'String', + /** + * `Int` scalar type is a signed non-fractional numerical value. + */ + int = 'Int', + /** + * `Float` scalar type is a signed double-precision fractional value. + */ + float = 'Float', + /** + * `Boolean` scalar type is a boolean value: true or false. + */ + boolean = 'Boolean', + + /** + * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates + */ + AWSDate = 'AWSDate', + /** + * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. + * + * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Times + */ + AWSTime = 'AWSTime', + /** + * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + */ + AWSDateTime = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWSTimestamp = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWSEmail = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWSJSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWSURL = 'AWSUrl', + /** + * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. + * + * The number can specify a country code at the beginning, but is not required for US phone numbers. + */ + AWSPhone = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWSIPAddress = 'AWSIPAddress', + + /** + * Type used for Object Types + */ + object = 'OBJECT', +} + +export interface SchemaTypeProps { + isList?: boolean; + isRequired?: boolean; + definition?: SchemaBaseType[]; + authorization?: AuthorizationType; +} + +abstract class SchemaBaseType { + public readonly type: SchemaType; + public readonly name: string; + public readonly isList: boolean; + public readonly isRequired: boolean; + + protected constructor( type: SchemaType, name: string, props?: SchemaTypeProps ) { + this.type = type; + this.name = name; + this.isList = props?.isList ?? false; + this.isRequired = props?.isRequired ?? false; + } +}; + +export class SchemaScalarType extends SchemaBaseType{ + public static id( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.id, name, props); + } + public static string( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.string, name, props); + } + public static int( name: string, props?: SchemaTypeProps): SchemaBaseType { + return new SchemaScalarType(SchemaType.int, name, props); + } + public static float( name: string, props?: SchemaTypeProps): SchemaScalarType { + return new SchemaScalarType(SchemaType.float, name, props); + } + public static boolean( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.boolean, name, props); + } + + public static AWSDate( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.AWSDate, name, props); + } + public static AWSTime( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.AWSTime, name, props); + } + public static AWSDateTime( name: string, props?: SchemaTypeProps): SchemaBaseType { + return new SchemaScalarType(SchemaType.AWSDateTime, name, props); + } + public static AWSTimestamp( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.AWSTimestamp, name, props); + } + public static AWSEmail( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.AWSEmail, name, props); + } + public static AWSJSON( name: string, props?: SchemaTypeProps): SchemaBaseType { + return new SchemaScalarType(SchemaType.AWSJSON, name, props); + } + public static AWSURL( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.AWSURL, name, props); + } + public static AWSPhone( name: string, props?: SchemaTypeProps ): SchemaScalarType { + return new SchemaScalarType(SchemaType.AWSPhone, name, props); + } + public static AWSIPAddress( name: string, props?: SchemaTypeProps): SchemaBaseType { + return new SchemaScalarType(SchemaType.AWSIPAddress, name, props); + } +} + +export class SchemaObjectType extends SchemaBaseType { + public static custom( name: string, props?: SchemaTypeProps ): SchemaObjectType { + if ( props == undefined || props.definition == undefined) { + throw Error('Custom Types must have a definition.'); + } + return new SchemaObjectType( SchemaType.object, name, props) + } + + public readonly definition?: SchemaBaseType[]; + public readonly authorization?: AuthorizationType; + + private constructor( type: SchemaType, name: string, props?: SchemaTypeProps ) { + super(type, name, props); + this.definition = props?.definition; + this.authorization = props?.authorization; + } + +} + +export class SchemaInterface { + +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-util.ts b/packages/@aws-cdk/aws-appsync/lib/schema-util.ts new file mode 100644 index 0000000000000..ea3138a736bfe --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-util.ts @@ -0,0 +1,142 @@ +import { AuthorizationType } from "./graphqlapi"; + +/** + * Enum containing the SchemaTypes that can be used to define ObjectTypes + */ +export enum Type { + /** + * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. + * + * Often used as a key for a cache and not intended to be human-readable. + */ + id = 'ID', + /** + * `String` scalar type is a free-form human-readable text. + */ + string = 'String', + /** + * `Int` scalar type is a signed non-fractional numerical value. + */ + int = 'Int', + /** + * `Float` scalar type is a signed double-precision fractional value. + */ + float = 'Float', + /** + * `Boolean` scalar type is a boolean value: true or false. + */ + boolean = 'Boolean', + + /** + * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates + */ + AWSDate = 'AWSDate', + /** + * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. + * + * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Times + */ + AWSTime = 'AWSTime', + /** + * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + */ + AWSDateTime = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWSTimestamp = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWSEmail = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWSJSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWSURL = 'AWSUrl', + /** + * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. + * + * The number can specify a country code at the beginning, but is not required for US phone numbers. + */ + AWSPhone = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWSIPAddress = 'AWSIPAddress', + + /** + * Type used for Object Types + */ + object = 'OBJECT', +} + +export interface SchemaTypeProps { + isList?: boolean; + isRequired?: boolean; + definition?: SchemaType[]; + authorization?: AuthorizationType; +} + +export class SchemaType { + public static id( name: string, props?: SchemaTypeProps ): SchemaType { + return new SchemaType(Type.id, name, props); + } + public static string( name: string, props?: SchemaTypeProps ): SchemaType { + return new SchemaType(Type.string, name, props); + } + public static int( name: string, props?: SchemaTypeProps): SchemaType { + return new SchemaType(Type.int, name, props); + } + public static float( name: string, props?: SchemaTypeProps): SchemaType { + return new SchemaType(Type.float, name, props); + } + public static boolean( name: string, props?: SchemaTypeProps ): SchemaType { + return new SchemaType(Type.boolean, name, props); + } + + public static custom( name: string, props?: SchemaTypeProps ): SchemaType { + if ( props == undefined || props.definition == undefined) { + throw Error('Custom Types must have a definition.'); + } + return new SchemaType( Type.object, name, props) + } + + public readonly type: Type; + public readonly name: string; + public readonly isList: boolean; + public readonly isRequired: boolean; + public readonly definition?: SchemaType[]; + public readonly authorization?: AuthorizationType; + + private constructor( type: Type, name: string, props?: SchemaTypeProps ) { + this.type = type; + this.name = name; + this.isList = props?.isList ?? false; + this.isRequired = props?.isRequired ?? false; + this.definition = props?.definition; + this.authorization = props?.authorization; + } + +} + +export class SchemaInterface { + +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts new file mode 100644 index 0000000000000..6354a569ec0f2 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -0,0 +1,14 @@ +import { CfnGraphQLSchema } from './appsync.generated'; +import { Construct } from '@aws-cdk/core'; +import { SchemaType } from './schema-util'; + +export interface SchemaProps { + +} + +export class Schema extends Construct { + constructor(scope: Construct, id: string, props: SchemaProps) { + super(scope, id); + + } +} \ No newline at end of file From 84a7b4c1af3c5b37c6a03d6cabc97e63a0a1ce79 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 23 Jul 2020 09:17:07 -0700 Subject: [PATCH 02/13] add doc strings to some classes --- packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts b/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts index 37b8c9324da85..0d9aa4d2ff767 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts @@ -88,6 +88,9 @@ export enum SchemaType { object = 'OBJECT', } +/** + * Properties of a GraphQL Schema Type + */ export interface SchemaTypeProps { isList?: boolean; isRequired?: boolean; @@ -95,6 +98,9 @@ export interface SchemaTypeProps { authorization?: AuthorizationType; } +/** + * Abstract base class for Types in GraphQL Schema + */ abstract class SchemaBaseType { public readonly type: SchemaType; public readonly name: string; From ede2020b0f11a8af9759b099fbb9e1a9e73dce22 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 23 Jul 2020 20:55:26 -0700 Subject: [PATCH 03/13] preliminary changes --- packages/@aws-cdk/aws-appsync/lib/schema.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index 6354a569ec0f2..a062128a67d77 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -3,12 +3,16 @@ import { Construct } from '@aws-cdk/core'; import { SchemaType } from './schema-util'; export interface SchemaProps { - + custom?: string; } export class Schema extends Construct { - constructor(scope: Construct, id: string, props: SchemaProps) { - super(scope, id); + public readonly custom?: string; + + constructor(scope: Construct, id: string, props?: SchemaProps) { + super(scope, id); + this.custom = props?.custom; } + } \ No newline at end of file From d9271ec2b21fa2241dc89273d05aebf893bbbdf7 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 27 Jul 2020 18:31:51 -0700 Subject: [PATCH 04/13] create enum for schemaDefinition --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 55 ++++-- .../aws-appsync/lib/schema-util-abstract.ts | 185 ------------------ .../@aws-cdk/aws-appsync/lib/schema-util.ts | 142 -------------- packages/@aws-cdk/aws-appsync/lib/schema.ts | 185 ++++++++++++++++-- .../aws-appsync/test/appsync-apikey.test.ts | 4 + .../aws-appsync/test/appsync-grant.test.ts | 1 + .../@aws-cdk/aws-appsync/test/appsync.test.ts | 1 + .../aws-appsync/test/integ.graphql-iam.ts | 2 + .../aws-appsync/test/integ.graphql.ts | 2 + 9 files changed, 224 insertions(+), 353 deletions(-) delete mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts delete mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-util.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 9678c0dd3fee9..d6dd2a92702c2 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -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 */ @@ -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. * - * @default - Use schemaDefinitionFile + * SchemaDefinition.CODE allows schema definition through CDK + * SchemaDefinition.FILE allows schema definition through outside file + * + * @experimental */ - readonly schemaDefinition?: string; + readonly schemaDefinition: SchemaDefinition; /** * File containing the GraphQL schema definition. You have to specify a definition or a file containing one. * @@ -404,18 +422,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.schemaDefinition, props.schemaDefinitionFile); } /** @@ -666,4 +673,22 @@ export class GraphQLApi extends Construct { const authModes = props.authorizationConfig?.additionalAuthorizationModes; return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined; } + + private defineSchema(mode: SchemaDefinition, file?: string): CfnGraphQLSchema { + if (mode == SchemaDefinition.CODE && file) { + throw new Error('You cant use the mode CODE and define and file. Change mode to FILE or unconfigure schemaDefinitionFile'); + } else if (mode == SchemaDefinition.FILE && !file) { + throw new Error('Missing Schema definition. Provide schemaDefinition or schemaDefinitionFile'); + } + let definition; + if (file) { + definition = readFileSync(file).toString('UTF-8'); + } else { + definition = ''; + } + return new CfnGraphQLSchema(this, 'Schema', { + apiId: this.apiId, + definition, + }); + } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts b/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts deleted file mode 100644 index 0d9aa4d2ff767..0000000000000 --- a/packages/@aws-cdk/aws-appsync/lib/schema-util-abstract.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { AuthorizationType } from "./graphqlapi"; - -/** - * Enum containing the SchemaTypes that can be used to define ObjectTypes - */ -export enum SchemaType { - /** - * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. - * - * Often used as a key for a cache and not intended to be human-readable. - */ - id = 'ID', - /** - * `String` scalar type is a free-form human-readable text. - */ - string = 'String', - /** - * `Int` scalar type is a signed non-fractional numerical value. - */ - int = 'Int', - /** - * `Float` scalar type is a signed double-precision fractional value. - */ - float = 'Float', - /** - * `Boolean` scalar type is a boolean value: true or false. - */ - boolean = 'Boolean', - - /** - * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates - */ - AWSDate = 'AWSDate', - /** - * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. - * - * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Times - */ - AWSTime = 'AWSTime', - /** - * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations - */ - AWSDateTime = 'AWSDateTime', - /** - * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. - * - * Timestamps are serialized and deserialized as numbers. - */ - AWSTimestamp = 'AWSTimestamp', - /** - * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) - */ - AWSEmail = 'AWSEmail', - /** - * `AWSJson` scalar type represents a JSON string. - */ - AWSJSON = 'AWSJSON', - /** - * `AWSURL` scalar type represetns a valid URL string. - * - * URLs wihtout schemes or contain double slashes are considered invalid. - */ - AWSURL = 'AWSUrl', - /** - * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. - * - * The number can specify a country code at the beginning, but is not required for US phone numbers. - */ - AWSPhone = 'AWSPhone', - /** - * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. - */ - AWSIPAddress = 'AWSIPAddress', - - /** - * Type used for Object Types - */ - object = 'OBJECT', -} - -/** - * Properties of a GraphQL Schema Type - */ -export interface SchemaTypeProps { - isList?: boolean; - isRequired?: boolean; - definition?: SchemaBaseType[]; - authorization?: AuthorizationType; -} - -/** - * Abstract base class for Types in GraphQL Schema - */ -abstract class SchemaBaseType { - public readonly type: SchemaType; - public readonly name: string; - public readonly isList: boolean; - public readonly isRequired: boolean; - - protected constructor( type: SchemaType, name: string, props?: SchemaTypeProps ) { - this.type = type; - this.name = name; - this.isList = props?.isList ?? false; - this.isRequired = props?.isRequired ?? false; - } -}; - -export class SchemaScalarType extends SchemaBaseType{ - public static id( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.id, name, props); - } - public static string( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.string, name, props); - } - public static int( name: string, props?: SchemaTypeProps): SchemaBaseType { - return new SchemaScalarType(SchemaType.int, name, props); - } - public static float( name: string, props?: SchemaTypeProps): SchemaScalarType { - return new SchemaScalarType(SchemaType.float, name, props); - } - public static boolean( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.boolean, name, props); - } - - public static AWSDate( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.AWSDate, name, props); - } - public static AWSTime( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.AWSTime, name, props); - } - public static AWSDateTime( name: string, props?: SchemaTypeProps): SchemaBaseType { - return new SchemaScalarType(SchemaType.AWSDateTime, name, props); - } - public static AWSTimestamp( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.AWSTimestamp, name, props); - } - public static AWSEmail( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.AWSEmail, name, props); - } - public static AWSJSON( name: string, props?: SchemaTypeProps): SchemaBaseType { - return new SchemaScalarType(SchemaType.AWSJSON, name, props); - } - public static AWSURL( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.AWSURL, name, props); - } - public static AWSPhone( name: string, props?: SchemaTypeProps ): SchemaScalarType { - return new SchemaScalarType(SchemaType.AWSPhone, name, props); - } - public static AWSIPAddress( name: string, props?: SchemaTypeProps): SchemaBaseType { - return new SchemaScalarType(SchemaType.AWSIPAddress, name, props); - } -} - -export class SchemaObjectType extends SchemaBaseType { - public static custom( name: string, props?: SchemaTypeProps ): SchemaObjectType { - if ( props == undefined || props.definition == undefined) { - throw Error('Custom Types must have a definition.'); - } - return new SchemaObjectType( SchemaType.object, name, props) - } - - public readonly definition?: SchemaBaseType[]; - public readonly authorization?: AuthorizationType; - - private constructor( type: SchemaType, name: string, props?: SchemaTypeProps ) { - super(type, name, props); - this.definition = props?.definition; - this.authorization = props?.authorization; - } - -} - -export class SchemaInterface { - -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-util.ts b/packages/@aws-cdk/aws-appsync/lib/schema-util.ts deleted file mode 100644 index ea3138a736bfe..0000000000000 --- a/packages/@aws-cdk/aws-appsync/lib/schema-util.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { AuthorizationType } from "./graphqlapi"; - -/** - * Enum containing the SchemaTypes that can be used to define ObjectTypes - */ -export enum Type { - /** - * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. - * - * Often used as a key for a cache and not intended to be human-readable. - */ - id = 'ID', - /** - * `String` scalar type is a free-form human-readable text. - */ - string = 'String', - /** - * `Int` scalar type is a signed non-fractional numerical value. - */ - int = 'Int', - /** - * `Float` scalar type is a signed double-precision fractional value. - */ - float = 'Float', - /** - * `Boolean` scalar type is a boolean value: true or false. - */ - boolean = 'Boolean', - - /** - * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates - */ - AWSDate = 'AWSDate', - /** - * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. - * - * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Times - */ - AWSTime = 'AWSTime', - /** - * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations - */ - AWSDateTime = 'AWSDateTime', - /** - * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. - * - * Timestamps are serialized and deserialized as numbers. - */ - AWSTimestamp = 'AWSTimestamp', - /** - * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) - */ - AWSEmail = 'AWSEmail', - /** - * `AWSJson` scalar type represents a JSON string. - */ - AWSJSON = 'AWSJSON', - /** - * `AWSURL` scalar type represetns a valid URL string. - * - * URLs wihtout schemes or contain double slashes are considered invalid. - */ - AWSURL = 'AWSUrl', - /** - * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. - * - * The number can specify a country code at the beginning, but is not required for US phone numbers. - */ - AWSPhone = 'AWSPhone', - /** - * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. - */ - AWSIPAddress = 'AWSIPAddress', - - /** - * Type used for Object Types - */ - object = 'OBJECT', -} - -export interface SchemaTypeProps { - isList?: boolean; - isRequired?: boolean; - definition?: SchemaType[]; - authorization?: AuthorizationType; -} - -export class SchemaType { - public static id( name: string, props?: SchemaTypeProps ): SchemaType { - return new SchemaType(Type.id, name, props); - } - public static string( name: string, props?: SchemaTypeProps ): SchemaType { - return new SchemaType(Type.string, name, props); - } - public static int( name: string, props?: SchemaTypeProps): SchemaType { - return new SchemaType(Type.int, name, props); - } - public static float( name: string, props?: SchemaTypeProps): SchemaType { - return new SchemaType(Type.float, name, props); - } - public static boolean( name: string, props?: SchemaTypeProps ): SchemaType { - return new SchemaType(Type.boolean, name, props); - } - - public static custom( name: string, props?: SchemaTypeProps ): SchemaType { - if ( props == undefined || props.definition == undefined) { - throw Error('Custom Types must have a definition.'); - } - return new SchemaType( Type.object, name, props) - } - - public readonly type: Type; - public readonly name: string; - public readonly isList: boolean; - public readonly isRequired: boolean; - public readonly definition?: SchemaType[]; - public readonly authorization?: AuthorizationType; - - private constructor( type: Type, name: string, props?: SchemaTypeProps ) { - this.type = type; - this.name = name; - this.isList = props?.isList ?? false; - this.isRequired = props?.isRequired ?? false; - this.definition = props?.definition; - this.authorization = props?.authorization; - } - -} - -export class SchemaInterface { - -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index a062128a67d77..779394044dbce 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -1,18 +1,181 @@ -import { CfnGraphQLSchema } from './appsync.generated'; -import { Construct } from '@aws-cdk/core'; -import { SchemaType } from './schema-util'; +import { AuthorizationType } from './graphqlapi'; -export interface SchemaProps { - custom?: string; +/** + * Enum containing the Types that can be used to define ObjectTypes + */ +export enum Type { + /** + * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. + * + * Often used as a key for a cache and not intended to be human-readable. + */ + id = 'ID', + /** + * `String` scalar type is a free-form human-readable text. + */ + string = 'String', + /** + * `Int` scalar type is a signed non-fractional numerical value. + */ + int = 'Int', + /** + * `Float` scalar type is a signed double-precision fractional value. + */ + float = 'Float', + /** + * `Boolean` scalar type is a boolean value: true or false. + */ + boolean = 'Boolean', + + /** + * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates + */ + AWSDate = 'AWSDate', + /** + * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. + * + * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Times + */ + AWSTime = 'AWSTime', + /** + * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + */ + AWSDateTime = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWSTimestamp = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWSEmail = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWSJSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWSURL = 'AWSUrl', + /** + * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. + * + * The number can specify a country code at the beginning, but is not required for US phone numbers. + */ + AWSPhone = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWSIPAddress = 'AWSIPAddress', + + /** + * Type used for Object Types + */ + object = 'OBJECT', +} + +/** + * Properties of a GraphQL Schema Type + */ +export interface TypeProps { + isList?: boolean; + isRequired?: boolean; + definition?: BaseType[]; + authorization?: AuthorizationType; +} + +/** + * Abstract base class for Types in GraphQL Schema + */ +abstract class BaseType { + public readonly type: Type; + public readonly name: string; + public readonly isList: boolean; + public readonly isRequired: boolean; + + protected constructor( type: Type, name: string, props?: TypeProps ) { + this.type = type; + this.name = name; + this.isList = props?.isList ?? false; + this.isRequired = props?.isRequired ?? false; + } +}; + +export class ScalarType extends BaseType{ + public static id( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.id, name, props); + } + public static string( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.string, name, props); + } + public static int( name: string, props?: TypeProps): BaseType { + return new ScalarType(Type.int, name, props); + } + public static float( name: string, props?: TypeProps): ScalarType { + return new ScalarType(Type.float, name, props); + } + public static boolean( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.boolean, name, props); + } + + public static AWSDate( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.AWSDate, name, props); + } + public static AWSTime( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.AWSTime, name, props); + } + public static AWSDateTime( name: string, props?: TypeProps): BaseType { + return new ScalarType(Type.AWSDateTime, name, props); + } + public static AWSTimestamp( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.AWSTimestamp, name, props); + } + public static AWSEmail( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.AWSEmail, name, props); + } + public static AWSJSON( name: string, props?: TypeProps): BaseType { + return new ScalarType(Type.AWSJSON, name, props); + } + public static AWSURL( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.AWSURL, name, props); + } + public static AWSPhone( name: string, props?: TypeProps ): ScalarType { + return new ScalarType(Type.AWSPhone, name, props); + } + public static AWSIPAddress( name: string, props?: TypeProps): BaseType { + return new ScalarType(Type.AWSIPAddress, name, props); + } } -export class Schema extends Construct { +export class ObjectType extends BaseType { + public static custom( name: string, props?: TypeProps ): ObjectType { + if ( props == undefined || props.definition == undefined) { + throw Error('Custom Types must have a definition.'); + } + return new ObjectType( Type.object, name, props); + } - public readonly custom?: string; + public readonly definition?: BaseType[]; + public readonly authorization?: AuthorizationType; - constructor(scope: Construct, id: string, props?: SchemaProps) { - super(scope, id); - this.custom = props?.custom; + private constructor( type: Type, name: string, props?: TypeProps ) { + super(type, name, props); + this.definition = props?.definition; + this.authorization = props?.authorization; } - + } \ No newline at end of file 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 da49317ee5a64..39a09c1f1c955 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts @@ -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'), }); @@ -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: { @@ -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: { @@ -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: { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts index f770cbad26319..5f8efefcb9f89 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts @@ -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: { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index 7a4a1fec1fab6..ec93a648efd1d 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -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'), }); 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 710975379030d..d55a63adcfb62 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts @@ -12,6 +12,7 @@ import { UserPoolDefaultAction, Values, IamResource, + SchemaDefinition, } from '../lib'; /* @@ -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: { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 7b3444cda5842..3b8564dba6865 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -10,6 +10,7 @@ import { PrimaryKey, UserPoolDefaultAction, Values, + SchemaDefinition, } from '../lib'; /* @@ -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: { From 7904e6230192464cc97f3ba233a5cde9f8973ee8 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 27 Jul 2020 19:56:16 -0700 Subject: [PATCH 05/13] adjusted SchemaDefinition to allow for S3 --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index d6dd2a92702c2..f32eb285e7eda 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -215,6 +215,11 @@ export enum SchemaDefinition { * Define schema in a file, i.e. schema.graphql */ FILE = 'FILE', + + /** + * Define schema in a S3 location + */ + S3 = 'S3', } /** @@ -350,6 +355,7 @@ export class GraphQLApi extends Construct { return this._apiKey; } + private schemaMode: SchemaDefinition; private api: CfnGraphQLApi; private _apiKey?: string; @@ -407,6 +413,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 || @@ -422,7 +429,7 @@ export class GraphQLApi extends Construct { this._apiKey = this.createAPIKey(apiKeyConfig); } - this.schema = this.defineSchema(props.schemaDefinition, props.schemaDefinitionFile); + this.schema = this.defineSchema(props.schemaDefinitionFile); } /** @@ -674,21 +681,43 @@ export class GraphQLApi extends Construct { return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined; } - private defineSchema(mode: SchemaDefinition, file?: string): CfnGraphQLSchema { - if (mode == SchemaDefinition.CODE && file) { - throw new Error('You cant use the mode CODE and define and file. Change mode to FILE or unconfigure schemaDefinitionFile'); - } else if (mode == SchemaDefinition.FILE && !file) { - throw new Error('Missing Schema definition. Provide schemaDefinition or schemaDefinitionFile'); + /** + * Add an object type + * @param name name of object type + */ + public addType (name: string): void{ + if ( this.schemaMode != SchemaDefinition.CODE ) { + throw new Error('API cannot add type because schema definition mode configured to CODE'); } + this.schema.definition = name; + } + + /** + * Define schema based on props configuration + * @param file the file name/s3 location of Schema + */ + private defineSchema(file?: string): CfnGraphQLSchema { let definition; - if (file) { + let definitionS3Location; + + 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 { + } else if ( this.schemaMode == SchemaDefinition.CODE && !file ) { definition = ''; + } else if ( this.schemaMode == SchemaDefinition.CODE && file) { + throw new Error('You cant use the mode CODE and define and file. Change mode to FILE/S3 or unconfigure schemaDefinitionFile'); + } else if ( this.schemaMode == SchemaDefinition.S3 && !file) { + throw new Error('schemaDefinitionFile must be configured if using S3 definition mode.'); + } else if ( this.schemaMode == SchemaDefinition.S3 && file ) { + definitionS3Location = file; } + return new CfnGraphQLSchema(this, 'Schema', { apiId: this.apiId, definition, + definitionS3Location, }); } } From 4591a7048dcf4c4b1c49323e7ac8958a8e41370a Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 27 Jul 2020 19:58:32 -0700 Subject: [PATCH 06/13] change addType to addDefinition for temporary inclusion --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index f32eb285e7eda..1fb7eb426ae81 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -685,7 +685,7 @@ export class GraphQLApi extends Construct { * Add an object type * @param name name of object type */ - public addType (name: string): void{ + public addDefinition (name: string): void{ if ( this.schemaMode != SchemaDefinition.CODE ) { throw new Error('API cannot add type because schema definition mode configured to CODE'); } From da20dcdcb73c0d1db9092002f364486d03df263b Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 28 Jul 2020 11:08:28 -0700 Subject: [PATCH 07/13] s3 testing --- packages/@aws-cdk/aws-appsync/README.md | 24 +- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 26 +-- packages/@aws-cdk/aws-appsync/package.json | 2 + .../aws-appsync/test/appsync-schema.test.ts | 180 +++++++++++++++ .../test/integ.graphql-s3.expected.json | 214 ++++++++++++++++++ .../aws-appsync/test/integ.graphql-s3.ts | 54 +++++ .../test/verify.integ.graphql-s3.sh | 21 ++ 7 files changed, 504 insertions(+), 17 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index f9811ddd9c1be..aa03bc607979c 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -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: { @@ -158,4 +159,25 @@ api.grantMutation(role, 'updateExample'); // For custom types and granular design api.grant(role, appsync.IamResource.ofType('Mutation', 'updateExample'), 'appsync:GraphQL'); -``` \ No newline at end of file +``` + +### s3-assets + +`aws-appsync` supports using s3 location for schema definition. + +```ts +import * as appsync from '@aws-cdk/aws-appsync'; +import * as assets from '@aws-cdk/aws-s3-assets'; + +const asset = new assets.Asset(stack, 'asset', { + path: path.join(__dirname, 'appsync.test.graphql'), +}); + +const api = new appsync.GraphQLApi(stack, 'api' { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.S3, + schemaDefinitionFile: asset.s3ObjectUrl, +}); +``` + +Use `s3-assets` when `schema.graphql` file is too large for CloudFormations. \ 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 1fb7eb426ae81..4d3a3352c5d62 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -451,11 +451,7 @@ export class GraphQLApi extends Construct { * @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 { + public addDynamoDbDataSource(name: string, description: string, table: ITable): DynamoDbDataSource { return new DynamoDbDataSource(this, `${name}DS`, { api: this, description, @@ -485,11 +481,7 @@ export class GraphQLApi extends Construct { * @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 { + public addLambdaDataSource(name: string, description: string, lambdaFunction: IFunction): LambdaDataSource { return new LambdaDataSource(this, `${name}DS`, { api: this, description, @@ -682,14 +674,16 @@ export class GraphQLApi extends Construct { } /** - * Add an object type - * @param name name of object type + * 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 addDefinition (name: string): void{ + public updateDefinition (definition: string): void{ if ( this.schemaMode != SchemaDefinition.CODE ) { - throw new Error('API cannot add type because schema definition mode configured to CODE'); + throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); } - this.schema.definition = name; + this.schema.definition = definition; } /** @@ -707,7 +701,7 @@ export class GraphQLApi extends Construct { } else if ( this.schemaMode == SchemaDefinition.CODE && !file ) { definition = ''; } else if ( this.schemaMode == SchemaDefinition.CODE && file) { - throw new Error('You cant use the mode CODE and define and file. Change mode to FILE/S3 or unconfigure schemaDefinitionFile'); + throw new Error('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile'); } else if ( this.schemaMode == SchemaDefinition.S3 && !file) { throw new Error('schemaDefinitionFile must be configured if using S3 definition mode.'); } else if ( this.schemaMode == SchemaDefinition.S3 && file ) { diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index ed431e29e7367..07fa3726c0f9b 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -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" }, @@ -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" }, diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts new file mode 100644 index 0000000000000..a306148850180 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -0,0 +1,180 @@ +import { join } from 'path'; +import '@aws-cdk/assert/jest'; +import * as assets from '@aws-cdk/aws-s3-assets'; +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.'); + }); + +}); + +describe('testing schema definition mode `s3`', () => { + + test('definition mode `s3` configures s3 location properly', () => { + // WHEN + new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.S3, + schemaDefinitionFile: 's3location', + }); + + //THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + DefinitionS3Location: 's3location', + }); + }); + + test('definition mode `s3` errors when calling updateDefiniton function', () => { + // WHEN + const api = new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.S3, + schemaDefinitionFile: 's3location', + }); + 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 `s3` errors when schemaDefinitionFile is not configured', () => { + // WHEN + const when = () => { + new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.S3, + }); + }; + + //THEN + expect(when).toThrowError('schemaDefinitionFile must be configured if using S3 definition mode.'); + }); + + test('definition mode `s3` properly configures s3-assets', () => { + // WHEN + const asset = new assets.Asset(stack, 'SampleAsset', { + path: join(__dirname, 'appsync.test.graphql'), + }); + new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.S3, + schemaDefinitionFile: asset.s3ObjectUrl, + }); + + //THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + DefinitionS3Location: { + 'Fn::Join': [ '', [ 's3://', + { Ref: 'AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3Bucket70236DA5' }, + '/', + { + 'Fn::Select': [ 0, { 'Fn::Split': [ + '||', + { Ref: 'AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E' }, + ] } ], + }, + { + 'Fn::Select': [ 1, { 'Fn::Split': [ + '||', + ] } ], + }, + ] ] }, + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json new file mode 100644 index 0000000000000..0b628227c7500 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json @@ -0,0 +1,214 @@ +{ + "Parameters": { + "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3Bucket70236DA5": { + "Type": "String", + "Description": "S3 bucket for asset \"2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229af\"" + }, + "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E": { + "Type": "String", + "Description": "S3 key for asset version \"2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229af\"" + }, + "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afArtifactHash546D8545": { + "Type": "String", + "Description": "Artifact hash for asset \"2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229af\"" + } + }, + "Resources": { + "ApiF70053CD": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "integ-test-s3" + } + }, + "ApiDefaultAPIKeyApiKey74F5313B": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "Description": "Default API Key created by CDK" + } + }, + "ApiSchema510EECD7": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "DefinitionS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3Bucket70236DA5" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E" + } + ] + } + ] + } + ] + ] + } + } + }, + "ApitestDataSourceDSServiceRoleE543E310": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C": { + "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": "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C", + "Roles": [ + { + "Ref": "ApitestDataSourceDSServiceRoleE543E310" + } + ] + } + }, + "ApitestDataSourceDS776EA507": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "Name": "testDataSource", + "Type": "AMAZON_DYNAMODB", + "Description": "Table for Tests\"", + "DynamoDBConfig": { + "AwsRegion": { + "Ref": "AWS::Region" + }, + "TableName": { + "Ref": "TestTable5769773A" + } + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "ApitestDataSourceDSServiceRoleE543E310", + "Arn" + ] + } + } + }, + "ApitestDataSourceDSQuerygetTestsResolver61ED88B6": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "ApiF70053CD", + "ApiId" + ] + }, + "FieldName": "getTests", + "TypeName": "Query", + "DataSourceName": "testDataSource", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "ApiSchema510EECD7", + "ApitestDataSourceDS776EA507" + ] + }, + "TestTable5769773A": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts new file mode 100644 index 0000000000000..81976f7ab42be --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts @@ -0,0 +1,54 @@ +import * as path from 'path'; +import * as db from '@aws-cdk/aws-dynamodb'; +import * as assets from '@aws-cdk/aws-s3-assets'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +/* + * Creates an Appsync GraphQL API with schema definition through + * s3 location. + * + * Stack verification steps: + * Deploy app and check if schema is defined. + * + * da2-z3xi4w55trgmxpzley2t7bfefe https://vymliuqndveudjq352yk4cuvg4.appsync-api.us-east-1.amazonaws.com/graphql + * + * -- cdk deploy --app 'node integ.graphql-s3.js' -- start -- + * -- aws appsync list-graphql-apis -- obtain api id && endpoint -- + * -- aws appsync list-api-keys --api-id [API ID] -- obtain api key -- + * -- bash verify.integ.graphql-s3.sh [APIKEY] [ENDPOINT] -- return with "getTests" -- + * -- cdk destroy --app 'node integ.graphql-s3.js' -- clean -- + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-appsync-integ'); + +const asset = new assets.Asset(stack, 'SampleAsset', { + path: path.join(__dirname, 'appsync.test.graphql'), +}); + +const api = new appsync.GraphQLApi(stack, 'Api', { + name: 'integ-test-s3', + schemaDefinition: appsync.SchemaDefinition.S3, + schemaDefinitionFile: asset.s3ObjectUrl, +}); + +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('testDataSource', 'Table for Tests"', testTable); + +testDS.createResolver({ + typeName: 'Query', + fieldName: 'getTests', + requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh new file mode 100644 index 0000000000000..e0d068ddf1bfc --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +function error { + printf "\e[91;5;81m$@\e[0m\n" +} + +function usage { + echo "#######################################################################" + echo "# run 'verify.integ.auth-apikey.sh [APIKEY] [ENDPOINT]' to run check #" + echo "#######################################################################" +} + +if [[ -z $1 || -z $2 ]]; then + error "Error: verification requires [APIKEY] [ENDPOINT]" + usage + exit 1 +fi + +echo THIS TEST SHOULD SUCCEED +curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:$1" -d '{ "query": "query { getTests { version } }" }" }' $2 +echo "" \ No newline at end of file From edf5c4745a054653d722be5a116d599ec2187afd Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 28 Jul 2020 11:10:11 -0700 Subject: [PATCH 08/13] remove schema.ts file for this PR --- packages/@aws-cdk/aws-appsync/lib/schema.ts | 181 -------------------- 1 file changed, 181 deletions(-) delete mode 100644 packages/@aws-cdk/aws-appsync/lib/schema.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts deleted file mode 100644 index 779394044dbce..0000000000000 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { AuthorizationType } from './graphqlapi'; - -/** - * Enum containing the Types that can be used to define ObjectTypes - */ -export enum Type { - /** - * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. - * - * Often used as a key for a cache and not intended to be human-readable. - */ - id = 'ID', - /** - * `String` scalar type is a free-form human-readable text. - */ - string = 'String', - /** - * `Int` scalar type is a signed non-fractional numerical value. - */ - int = 'Int', - /** - * `Float` scalar type is a signed double-precision fractional value. - */ - float = 'Float', - /** - * `Boolean` scalar type is a boolean value: true or false. - */ - boolean = 'Boolean', - - /** - * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates - */ - AWSDate = 'AWSDate', - /** - * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. - * - * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Times - */ - AWSTime = 'AWSTime', - /** - * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations - */ - AWSDateTime = 'AWSDateTime', - /** - * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. - * - * Timestamps are serialized and deserialized as numbers. - */ - AWSTimestamp = 'AWSTimestamp', - /** - * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) - */ - AWSEmail = 'AWSEmail', - /** - * `AWSJson` scalar type represents a JSON string. - */ - AWSJSON = 'AWSJSON', - /** - * `AWSURL` scalar type represetns a valid URL string. - * - * URLs wihtout schemes or contain double slashes are considered invalid. - */ - AWSURL = 'AWSUrl', - /** - * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. - * - * The number can specify a country code at the beginning, but is not required for US phone numbers. - */ - AWSPhone = 'AWSPhone', - /** - * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. - */ - AWSIPAddress = 'AWSIPAddress', - - /** - * Type used for Object Types - */ - object = 'OBJECT', -} - -/** - * Properties of a GraphQL Schema Type - */ -export interface TypeProps { - isList?: boolean; - isRequired?: boolean; - definition?: BaseType[]; - authorization?: AuthorizationType; -} - -/** - * Abstract base class for Types in GraphQL Schema - */ -abstract class BaseType { - public readonly type: Type; - public readonly name: string; - public readonly isList: boolean; - public readonly isRequired: boolean; - - protected constructor( type: Type, name: string, props?: TypeProps ) { - this.type = type; - this.name = name; - this.isList = props?.isList ?? false; - this.isRequired = props?.isRequired ?? false; - } -}; - -export class ScalarType extends BaseType{ - public static id( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.id, name, props); - } - public static string( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.string, name, props); - } - public static int( name: string, props?: TypeProps): BaseType { - return new ScalarType(Type.int, name, props); - } - public static float( name: string, props?: TypeProps): ScalarType { - return new ScalarType(Type.float, name, props); - } - public static boolean( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.boolean, name, props); - } - - public static AWSDate( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.AWSDate, name, props); - } - public static AWSTime( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.AWSTime, name, props); - } - public static AWSDateTime( name: string, props?: TypeProps): BaseType { - return new ScalarType(Type.AWSDateTime, name, props); - } - public static AWSTimestamp( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.AWSTimestamp, name, props); - } - public static AWSEmail( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.AWSEmail, name, props); - } - public static AWSJSON( name: string, props?: TypeProps): BaseType { - return new ScalarType(Type.AWSJSON, name, props); - } - public static AWSURL( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.AWSURL, name, props); - } - public static AWSPhone( name: string, props?: TypeProps ): ScalarType { - return new ScalarType(Type.AWSPhone, name, props); - } - public static AWSIPAddress( name: string, props?: TypeProps): BaseType { - return new ScalarType(Type.AWSIPAddress, name, props); - } -} - -export class ObjectType extends BaseType { - public static custom( name: string, props?: TypeProps ): ObjectType { - if ( props == undefined || props.definition == undefined) { - throw Error('Custom Types must have a definition.'); - } - return new ObjectType( Type.object, name, props); - } - - public readonly definition?: BaseType[]; - public readonly authorization?: AuthorizationType; - - private constructor( type: Type, name: string, props?: TypeProps ) { - super(type, name, props); - this.definition = props?.definition; - this.authorization = props?.authorization; - } - -} \ No newline at end of file From 1aa37cb0a9cd2b500541cf2898c717d667928718 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 28 Jul 2020 11:12:50 -0700 Subject: [PATCH 09/13] restore ugly formating to prevent merge conflicts --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 4d3a3352c5d62..606c518b33e1a 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -451,7 +451,11 @@ export class GraphQLApi extends Construct { * @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 { + public addDynamoDbDataSource( + name: string, + description: string, + table: ITable, + ): DynamoDbDataSource { return new DynamoDbDataSource(this, `${name}DS`, { api: this, description, @@ -481,7 +485,11 @@ export class GraphQLApi extends Construct { * @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 { + public addLambdaDataSource( + name: string, + description: string, + lambdaFunction: IFunction, + ): LambdaDataSource { return new LambdaDataSource(this, `${name}DS`, { api: this, description, From 120a21f8a3ca1e8c099abce0e444bd32c316761a Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 28 Jul 2020 11:24:39 -0700 Subject: [PATCH 10/13] add doc for s3 --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 606c518b33e1a..c9a2ec4deee3f 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -250,7 +250,8 @@ export interface GraphQLApiProps { * GraphQL schema definition. Specify how you want to define your schema. * * SchemaDefinition.CODE allows schema definition through CDK - * SchemaDefinition.FILE allows schema definition through outside file + * SchemaDefinition.FILE allows schema definition through schema.graphql file + * SchemaDefinition.S3 allows schema definition through s3 location * * @experimental */ From 0cf8100310903a133611f862766885f513a84b76 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 30 Jul 2020 15:21:29 -0700 Subject: [PATCH 11/13] cleanse s3 implementation --- packages/@aws-cdk/aws-appsync/README.md | 23 +- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 12 - .../aws-appsync/test/appsync-schema.test.ts | 75 ------ .../test/integ.graphql-s3.expected.json | 214 ------------------ .../aws-appsync/test/integ.graphql-s3.ts | 54 ----- .../test/verify.integ.graphql-s3.sh | 21 -- 6 files changed, 1 insertion(+), 398 deletions(-) delete mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json delete mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts delete mode 100644 packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index aa03bc607979c..40064e0822e02 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -159,25 +159,4 @@ api.grantMutation(role, 'updateExample'); // For custom types and granular design api.grant(role, appsync.IamResource.ofType('Mutation', 'updateExample'), 'appsync:GraphQL'); -``` - -### s3-assets - -`aws-appsync` supports using s3 location for schema definition. - -```ts -import * as appsync from '@aws-cdk/aws-appsync'; -import * as assets from '@aws-cdk/aws-s3-assets'; - -const asset = new assets.Asset(stack, 'asset', { - path: path.join(__dirname, 'appsync.test.graphql'), -}); - -const api = new appsync.GraphQLApi(stack, 'api' { - name: 'api', - schemaDefinition: appsync.SchemaDefinition.S3, - schemaDefinitionFile: asset.s3ObjectUrl, -}); -``` - -Use `s3-assets` when `schema.graphql` file is too large for CloudFormations. \ No newline at end of file +``` \ 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 c9a2ec4deee3f..ce9c380d3707a 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -215,11 +215,6 @@ export enum SchemaDefinition { * Define schema in a file, i.e. schema.graphql */ FILE = 'FILE', - - /** - * Define schema in a S3 location - */ - S3 = 'S3', } /** @@ -251,7 +246,6 @@ export interface GraphQLApiProps { * * SchemaDefinition.CODE allows schema definition through CDK * SchemaDefinition.FILE allows schema definition through schema.graphql file - * SchemaDefinition.S3 allows schema definition through s3 location * * @experimental */ @@ -701,7 +695,6 @@ export class GraphQLApi extends Construct { */ private defineSchema(file?: string): CfnGraphQLSchema { let definition; - let definitionS3Location; if ( this.schemaMode == SchemaDefinition.FILE && !file) { throw new Error('schemaDefinitionFile must be configured if using FILE definition mode.'); @@ -711,16 +704,11 @@ export class GraphQLApi extends Construct { 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'); - } else if ( this.schemaMode == SchemaDefinition.S3 && !file) { - throw new Error('schemaDefinitionFile must be configured if using S3 definition mode.'); - } else if ( this.schemaMode == SchemaDefinition.S3 && file ) { - definitionS3Location = file; } return new CfnGraphQLSchema(this, 'Schema', { apiId: this.apiId, definition, - definitionS3Location, }); } } diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts index a306148850180..64158bbef0a1d 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -102,79 +102,4 @@ describe('testing schema definition mode `file`', () => { expect(when).toThrowError('schemaDefinitionFile must be configured if using FILE definition mode.'); }); -}); - -describe('testing schema definition mode `s3`', () => { - - test('definition mode `s3` configures s3 location properly', () => { - // WHEN - new appsync.GraphQLApi(stack, 'API', { - name: 'demo', - schemaDefinition: appsync.SchemaDefinition.S3, - schemaDefinitionFile: 's3location', - }); - - //THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - DefinitionS3Location: 's3location', - }); - }); - - test('definition mode `s3` errors when calling updateDefiniton function', () => { - // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { - name: 'demo', - schemaDefinition: appsync.SchemaDefinition.S3, - schemaDefinitionFile: 's3location', - }); - 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 `s3` errors when schemaDefinitionFile is not configured', () => { - // WHEN - const when = () => { - new appsync.GraphQLApi(stack, 'API', { - name: 'demo', - schemaDefinition: appsync.SchemaDefinition.S3, - }); - }; - - //THEN - expect(when).toThrowError('schemaDefinitionFile must be configured if using S3 definition mode.'); - }); - - test('definition mode `s3` properly configures s3-assets', () => { - // WHEN - const asset = new assets.Asset(stack, 'SampleAsset', { - path: join(__dirname, 'appsync.test.graphql'), - }); - new appsync.GraphQLApi(stack, 'API', { - name: 'demo', - schemaDefinition: appsync.SchemaDefinition.S3, - schemaDefinitionFile: asset.s3ObjectUrl, - }); - - //THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - DefinitionS3Location: { - 'Fn::Join': [ '', [ 's3://', - { Ref: 'AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3Bucket70236DA5' }, - '/', - { - 'Fn::Select': [ 0, { 'Fn::Split': [ - '||', - { Ref: 'AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E' }, - ] } ], - }, - { - 'Fn::Select': [ 1, { 'Fn::Split': [ - '||', - ] } ], - }, - ] ] }, - }); - }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json deleted file mode 100644 index 0b628227c7500..0000000000000 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.expected.json +++ /dev/null @@ -1,214 +0,0 @@ -{ - "Parameters": { - "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3Bucket70236DA5": { - "Type": "String", - "Description": "S3 bucket for asset \"2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229af\"" - }, - "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E": { - "Type": "String", - "Description": "S3 key for asset version \"2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229af\"" - }, - "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afArtifactHash546D8545": { - "Type": "String", - "Description": "Artifact hash for asset \"2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229af\"" - } - }, - "Resources": { - "ApiF70053CD": { - "Type": "AWS::AppSync::GraphQLApi", - "Properties": { - "AuthenticationType": "API_KEY", - "Name": "integ-test-s3" - } - }, - "ApiDefaultAPIKeyApiKey74F5313B": { - "Type": "AWS::AppSync::ApiKey", - "Properties": { - "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "ApiId" - ] - }, - "Description": "Default API Key created by CDK" - } - }, - "ApiSchema510EECD7": { - "Type": "AWS::AppSync::GraphQLSchema", - "Properties": { - "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "ApiId" - ] - }, - "DefinitionS3Location": { - "Fn::Join": [ - "", - [ - "s3://", - { - "Ref": "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3Bucket70236DA5" - }, - "/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AssetParameters2934f8056bab9557d749cf667bb22ad4f5778fa0b33a0d97e1fa0181efc229afS3VersionKeyC472828E" - } - ] - } - ] - } - ] - ] - } - } - }, - "ApitestDataSourceDSServiceRoleE543E310": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "appsync.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C": { - "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": "ApitestDataSourceDSServiceRoleDefaultPolicy53D2252C", - "Roles": [ - { - "Ref": "ApitestDataSourceDSServiceRoleE543E310" - } - ] - } - }, - "ApitestDataSourceDS776EA507": { - "Type": "AWS::AppSync::DataSource", - "Properties": { - "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "ApiId" - ] - }, - "Name": "testDataSource", - "Type": "AMAZON_DYNAMODB", - "Description": "Table for Tests\"", - "DynamoDBConfig": { - "AwsRegion": { - "Ref": "AWS::Region" - }, - "TableName": { - "Ref": "TestTable5769773A" - } - }, - "ServiceRoleArn": { - "Fn::GetAtt": [ - "ApitestDataSourceDSServiceRoleE543E310", - "Arn" - ] - } - } - }, - "ApitestDataSourceDSQuerygetTestsResolver61ED88B6": { - "Type": "AWS::AppSync::Resolver", - "Properties": { - "ApiId": { - "Fn::GetAtt": [ - "ApiF70053CD", - "ApiId" - ] - }, - "FieldName": "getTests", - "TypeName": "Query", - "DataSourceName": "testDataSource", - "Kind": "UNIT", - "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}", - "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" - }, - "DependsOn": [ - "ApiSchema510EECD7", - "ApitestDataSourceDS776EA507" - ] - }, - "TestTable5769773A": { - "Type": "AWS::DynamoDB::Table", - "Properties": { - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "AttributeDefinitions": [ - { - "AttributeName": "id", - "AttributeType": "S" - } - ], - "BillingMode": "PAY_PER_REQUEST" - }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts deleted file mode 100644 index 81976f7ab42be..0000000000000 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-s3.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as path from 'path'; -import * as db from '@aws-cdk/aws-dynamodb'; -import * as assets from '@aws-cdk/aws-s3-assets'; -import * as cdk from '@aws-cdk/core'; -import * as appsync from '../lib'; - -/* - * Creates an Appsync GraphQL API with schema definition through - * s3 location. - * - * Stack verification steps: - * Deploy app and check if schema is defined. - * - * da2-z3xi4w55trgmxpzley2t7bfefe https://vymliuqndveudjq352yk4cuvg4.appsync-api.us-east-1.amazonaws.com/graphql - * - * -- cdk deploy --app 'node integ.graphql-s3.js' -- start -- - * -- aws appsync list-graphql-apis -- obtain api id && endpoint -- - * -- aws appsync list-api-keys --api-id [API ID] -- obtain api key -- - * -- bash verify.integ.graphql-s3.sh [APIKEY] [ENDPOINT] -- return with "getTests" -- - * -- cdk destroy --app 'node integ.graphql-s3.js' -- clean -- - */ - -const app = new cdk.App(); -const stack = new cdk.Stack(app, 'aws-appsync-integ'); - -const asset = new assets.Asset(stack, 'SampleAsset', { - path: path.join(__dirname, 'appsync.test.graphql'), -}); - -const api = new appsync.GraphQLApi(stack, 'Api', { - name: 'integ-test-s3', - schemaDefinition: appsync.SchemaDefinition.S3, - schemaDefinitionFile: asset.s3ObjectUrl, -}); - -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('testDataSource', 'Table for Tests"', testTable); - -testDS.createResolver({ - typeName: 'Query', - fieldName: 'getTests', - requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(), - responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(), -}); - -app.synth(); diff --git a/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh deleted file mode 100644 index e0d068ddf1bfc..0000000000000 --- a/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-s3.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -function error { - printf "\e[91;5;81m$@\e[0m\n" -} - -function usage { - echo "#######################################################################" - echo "# run 'verify.integ.auth-apikey.sh [APIKEY] [ENDPOINT]' to run check #" - echo "#######################################################################" -} - -if [[ -z $1 || -z $2 ]]; then - error "Error: verification requires [APIKEY] [ENDPOINT]" - usage - exit 1 -fi - -echo THIS TEST SHOULD SUCCEED -curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:$1" -d '{ "query": "query { getTests { version } }" }" }' $2 -echo "" \ No newline at end of file From ccbc5e4d227ea4bdc643461cd05f6c86f85c3a22 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 30 Jul 2020 15:28:39 -0700 Subject: [PATCH 12/13] last of removing remanants of s3 --- packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts index 64158bbef0a1d..d031dbf77d542 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -1,6 +1,5 @@ import { join } from 'path'; import '@aws-cdk/assert/jest'; -import * as assets from '@aws-cdk/aws-s3-assets'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; From 6849e84cd67d0cd444cdda94cf25db3272540a31 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 30 Jul 2020 16:10:59 -0700 Subject: [PATCH 13/13] fix merge errors --- packages/@aws-cdk/aws-appsync/test/appsync.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index 0621a168602cd..70e8ef0082b89 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -29,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'), }); @@ -54,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'), }); @@ -77,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'), });