From 46df9f06c9d1af435fda0586739bcdb7e80312c5 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 22 Jul 2020 14:31:28 -0700 Subject: [PATCH 01/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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 4d48a2d7f1a0a0b2c31ace23ee584618a12b6127 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 08:58:03 -0700 Subject: [PATCH 08/78] prelim changes --- packages/@aws-cdk/aws-appsync/lib/index.ts | 1 + packages/@aws-cdk/aws-appsync/lib/schema.ts | 25 +- .../test/integ.graphql-s3.expected.json | 214 ------------------ .../aws-appsync/test/integ.graphql-s3.ts | 54 ----- .../test/verify.integ.graphql-s3.sh | 21 -- 5 files changed, 21 insertions(+), 294 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/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index 852cbed1c54f6..df12a76471490 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -4,4 +4,5 @@ export * from './key'; export * from './data-source'; export * from './mapping-template'; export * from './resolver'; +export * from './schema'; export * from './graphqlapi'; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index 779394044dbce..0838b20047ab8 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -1,5 +1,3 @@ -import { AuthorizationType } from './graphqlapi'; - /** * Enum containing the Types that can be used to define ObjectTypes */ @@ -88,6 +86,23 @@ export enum Type { object = 'OBJECT', } +export class Directive { + public static iam(): Directive{ + return new Directive('@aws_iam'); + } + + private constructor(public readonly statement: string) {} +} + +export interface IType { + type: Type + name: string; + isList: boolean; + isRequired: boolean; + definition?: IType[]; + directives?: Directive[]; +} + /** * Properties of a GraphQL Schema Type */ @@ -95,7 +110,7 @@ export interface TypeProps { isList?: boolean; isRequired?: boolean; definition?: BaseType[]; - authorization?: AuthorizationType; + directives?: Directive[]; } /** @@ -170,12 +185,12 @@ export class ObjectType extends BaseType { } public readonly definition?: BaseType[]; - public readonly authorization?: AuthorizationType; + public readonly directives?: Directive[]; private constructor( type: Type, name: string, props?: TypeProps ) { super(type, name, props); this.definition = props?.definition; - this.authorization = props?.authorization; + this.directives = props?.directives; } } \ 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 63e35b34e969ab627764af1cfd1764cd3eae2ac6 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:19:05 -0700 Subject: [PATCH 09/78] prelim run of addType --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 74 +++- packages/@aws-cdk/aws-appsync/lib/index.ts | 2 +- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 322 ++++++++++++++++++ packages/@aws-cdk/aws-appsync/lib/schema.ts | 196 ----------- .../aws-appsync/test/integ.graphql-schema.ts | 35 ++ 5 files changed, 420 insertions(+), 209 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-types.ts delete mode 100644 packages/@aws-cdk/aws-appsync/lib/schema.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index dd9284c496bb1..ba9bc77ebe047 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -6,7 +6,7 @@ import { IFunction } from '@aws-cdk/aws-lambda'; import { Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; -import { Directive, IType, ObjectType } from './schema'; +import { ObjectType, ObjectTypeProps, Directive, AttributeType } from './schema-types'; /** * enum with all possible values for AppSync authorization type @@ -319,14 +319,6 @@ export class IamResource { } } -/** - * The definition of an object type - */ -export interface TypeDefinition { - definition: IType[]; - directives?: Directive[]; -} - /** * An AppSync GraphQL API */ @@ -695,18 +687,76 @@ export class GraphQLApi extends Construct { /** * Add an object type to the schema * - * @param name the name of the object type + * @param name the name of the object type * @param props the definition */ - public addType(name: string, props: TypeDefinition): ObjectType { - const type = ObjectType.custom(name, { + public addType(name: string, props: ObjectTypeProps): ObjectType { + if ( this.schemaMode != SchemaDefinition.CODE ) { + throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); + } + const type = new ObjectType(name, { definition: props.definition, directives: props.directives, }); this.types.push(type); + this.generateObjectType(type); return type; } + /** + * Utility function to generate directives + * + * @param directives the directives of a given type + * @param delimiter the separator betweeen directives + */ + private generateDirectives (directives?: Directive[], delimiter?: string): string{ + if (!directives){ + return ''; + } + const sep = delimiter ?? ' '; + let schemaAddition = ''; + directives.map((directive) => { + schemaAddition = `${schemaAddition}${directive.statement}${sep}`; + }); + return schemaAddition; + } + + /** + * Generate object type + */ + private generateObjectType(type: ObjectType): void { + const directives = this.generateDirectives(type.directives); + let schemaAddition = `type ${type.name} ${directives}{\n`; + type.definition.map((def) => { + const attribute = this.generateAttribute(def); + schemaAddition = `${schemaAddition} ${attribute}\n`; + }); + this.appendToSchema(`${schemaAddition}}`); + } + + /** + * Escape hatch to append to Schema as desired + * + * @param addition the addition to add to schema + * @param delimiter the delimiter between schema and addition + * @default - ' ' + */ + public appendToSchema(addition: string, delimiter?: string): void { + const sep = delimiter ?? ''; + this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; + } + + /** + * Generate string for attributes + * + * @param attribute - the attribute of a type + */ + private generateAttribute(attribute: AttributeType): string { + const list = attribute.isList ? '[]' : ''; + const required = attribute.isRequired ? '!' : ''; + return `${attribute.name}: ${attribute.type}${list}${required}`; + } + /** * Define schema based on props configuration * @param file the file name/s3 location of Schema diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index df12a76471490..e0b60e1451a52 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -4,5 +4,5 @@ export * from './key'; export * from './data-source'; export * from './mapping-template'; export * from './resolver'; -export * from './schema'; +export * from './schema-types'; export * from './graphqlapi'; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts new file mode 100644 index 0000000000000..f5a8f2672b5bc --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -0,0 +1,322 @@ +/** + * Directives for types + * + * i.e. @aws_iam or @aws_subscribe + */ +export class Directive { + /** + * Add the @aws_iam directive + */ + public static iam(): Directive{ + return new Directive('@aws_iam'); + } + + /** + * Add a custom directive + * + * @param statement - the directive statement to append + * Note: doesn't guarantee functionality + */ + public static custom(statement: string): Directive { + return new Directive(statement); + } + + /** + * the directive statement + */ + public readonly statement: string; + + private constructor(statement: string) { this.statement = statement; } +} + +/** + * Properties of a GraphQL Schema Type + */ +export interface BaseTypeProps { + /** + * property declaring if this type is a list + * + * @default - false + */ + readonly isList?: boolean; + + /** + * property declaring if this type is required + * + * @default - false + */ + readonly isRequired?: boolean; +} + +/** + * Properties for configuring an Object Type + */ +export interface ObjectTypeProps { + /** + * the attributes of this object type + */ + readonly definition: AttributeType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + readonly directives?: Directive []; +} + +/** + * Object Types must be configured using addType to be added to schema + */ +export class ObjectType { + /** + * the name of this object type + */ + public readonly name: string; + /** + * the attributes of this object type + */ + public readonly definition: AttributeType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + public readonly directives?: Directive[]; + + public constructor(name: string, props: ObjectTypeProps) { + this.name = name; + this.definition = props.definition; + this.directives = props?.directives; + } + +} + +/** + * The Scalar Types in AppSync's GraphQL + */ +export class AttributeType { + /** + * `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. + */ + public static id(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.ID, name, props); + } + /** + * `String` scalar type is a free-form human-readable text. + */ + public static string(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.STRING, name, props); + } + /** + * `Int` scalar type is a signed non-fractional numerical value. + */ + public static int(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.INT, name, props); + } + /** + * `Float` scalar type is a signed double-precision fractional value. + */ + public static float(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.FLOAT, name, props); + } + /** + * `Boolean` scalar type is a boolean value: true or false. + */ + public static boolean(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.BOOLEAN, name, props); + } + + /** + * `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 + */ + public static awsDate(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_DATE, name, props); + } + /** + * `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 + */ + public static awsTime(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_TIME, name, props); + } + /** + * `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 + */ + public static awsDateTime(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_DATE_TIME, name, props); + } + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + public static awsTimestamp(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_TIMESTAMP, name, props); + } + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + public static awsEmail(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_EMAIL, name, props); + } + /** + * `AWSJson` scalar type represents a JSON string. + */ + public static awsJSON(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_JSON, name, props); + } + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + public static awsURL(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_URL, name, props); + } + /** + * `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. + */ + public static awsPhone(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_PHONE, name, props); + } + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + public static awsIpAddress(name: string, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.AWS_IP_ADDRESS, name, props); + } + + /** + * an object type to be added as an attribute + */ + public static object(type: ObjectType, props?: BaseTypeProps): AttributeType { + return new AttributeType(Type.OBJECT, type.name, props); + } + + /** + * the type of attribute + */ + public readonly type: Type; + /** + * the name of this attribute type + */ + public readonly name: string; + /** + * property determining if this attribute is a list + */ + public readonly isList: boolean; + /** + * property determining if this attribute is required + */ + public readonly isRequired: boolean; + + private constructor(type: Type, name: string, props?: BaseTypeProps) { + this.type = type; + this.name = name; + this.isList = props?.isList ?? false; + this.isRequired = props?.isRequired ?? false; + } +} + +/** + * 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 + */ + AWS_DATE = '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 + */ + AWS_TIME = '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 + */ + AWS_DATE_TIME = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWS_TIMESTAMP = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWS_EMAIL = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWS_JSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWS_URL = '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. + */ + AWS_PHONE = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWS_IP_ADDRESS = 'AWSIPAddress', + + /** + * Type used for Object Types + */ + OBJECT = 'OBJECT', +} \ 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 deleted file mode 100644 index 0838b20047ab8..0000000000000 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * 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', -} - -export class Directive { - public static iam(): Directive{ - return new Directive('@aws_iam'); - } - - private constructor(public readonly statement: string) {} -} - -export interface IType { - type: Type - name: string; - isList: boolean; - isRequired: boolean; - definition?: IType[]; - directives?: Directive[]; -} - -/** - * Properties of a GraphQL Schema Type - */ -export interface TypeProps { - isList?: boolean; - isRequired?: boolean; - definition?: BaseType[]; - directives?: Directive[]; -} - -/** - * 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 directives?: Directive[]; - - private constructor( type: Type, name: string, props?: TypeProps ) { - super(type, name, props); - this.definition = props?.definition; - this.directives = props?.directives; - } - -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts new file mode 100644 index 0000000000000..25f3ad057a8ea --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -0,0 +1,35 @@ +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +/* + * Creates an Appsync GraphQL API and schema in a code-first approach. + * + * Stack verification steps: + * + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'code-first-schema'); + +const api = new appsync.GraphQLApi(stack, 'code-first-api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, +}); + +api.addType('test', { + definition: [ + appsync.ScalarType.string('id', { + isRequired: true, + }), + appsync.ScalarType.string('version', { + isRequired: true, + }), + ], + directives: [ + appsync.Directive.iam(), + ], +}); + +api.appendToSchema('type Query {\n getTest(id: String!): test\n}', '\n'); + +app.synth(); \ No newline at end of file From 1b823404770f72b244c8be8a035b0b08e4c04d31 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:23:50 -0700 Subject: [PATCH 10/78] reorg schema functions --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index ba9bc77ebe047..9ffcc36ab7a75 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -671,6 +671,29 @@ export class GraphQLApi extends Construct { return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined; } + /** + * 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, + }); + } + /** * Sets schema defintiion to input if schema mode is configured with SchemaDefinition.CODE * @@ -679,11 +702,26 @@ export class GraphQLApi extends Construct { */ 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.'); + throw new Error('API cannot update schema because schema definition mode is not configured as CODE.'); } this.schema.definition = definition; } + /** + * Escape hatch to append to Schema as desired + * + * @param addition the addition to add to schema + * @param delimiter the delimiter between schema and addition + * @default - ' ' + */ + public appendToSchema(addition: string, delimiter?: string): void { + if ( this.schemaMode != SchemaDefinition.CODE ) { + throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); + } + const sep = delimiter ?? ''; + this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; + } + /** * Add an object type to the schema * @@ -734,18 +772,6 @@ export class GraphQLApi extends Construct { this.appendToSchema(`${schemaAddition}}`); } - /** - * Escape hatch to append to Schema as desired - * - * @param addition the addition to add to schema - * @param delimiter the delimiter between schema and addition - * @default - ' ' - */ - public appendToSchema(addition: string, delimiter?: string): void { - const sep = delimiter ?? ''; - this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; - } - /** * Generate string for attributes * @@ -756,27 +782,4 @@ export class GraphQLApi extends Construct { const required = attribute.isRequired ? '!' : ''; return `${attribute.name}: ${attribute.type}${list}${required}`; } - - /** - * 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, - }); - } } From fad768a831092f1d0a3394a33f7396d72945d63e Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:24:22 -0700 Subject: [PATCH 11/78] == to === --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 9ffcc36ab7a75..cd925c860304a 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -678,13 +678,13 @@ export class GraphQLApi extends Construct { private defineSchema(file?: string): CfnGraphQLSchema { let definition; - if ( this.schemaMode == SchemaDefinition.FILE && !file) { + 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 ) { + } else if ( this.schemaMode === SchemaDefinition.FILE && file ) { definition = readFileSync(file).toString('UTF-8'); - } else if ( this.schemaMode == SchemaDefinition.CODE && !file ) { + } else if ( this.schemaMode === SchemaDefinition.CODE && !file ) { definition = ''; - } else if ( this.schemaMode == SchemaDefinition.CODE && file) { + } 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'); } From 5bec4822363719711182e8db698c1fbf362b955e Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:26:02 -0700 Subject: [PATCH 12/78] chore up schema functions --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index cd925c860304a..c4517322bd970 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -712,7 +712,7 @@ export class GraphQLApi extends Construct { * * @param addition the addition to add to schema * @param delimiter the delimiter between schema and addition - * @default - ' ' + * @default - '' */ public appendToSchema(addition: string, delimiter?: string): void { if ( this.schemaMode != SchemaDefinition.CODE ) { @@ -746,15 +746,13 @@ export class GraphQLApi extends Construct { * * @param directives the directives of a given type * @param delimiter the separator betweeen directives + * @default - ' ' */ private generateDirectives (directives?: Directive[], delimiter?: string): string{ - if (!directives){ - return ''; - } - const sep = delimiter ?? ' '; let schemaAddition = ''; + if (!directives){ return schemaAddition; } directives.map((directive) => { - schemaAddition = `${schemaAddition}${directive.statement}${sep}`; + schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; }); return schemaAddition; } From 96021db9d26125374d15f1c6e7d873fd2bdb8318 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:28:02 -0700 Subject: [PATCH 13/78] remove readme section on s3 assets --- packages/@aws-cdk/aws-appsync/README.md | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) 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 From 0184c25e4cc32c2a18b362bed8d7e2433e380ee5 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:29:11 -0700 Subject: [PATCH 14/78] remove unneccessary prop this.types --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index c4517322bd970..f738b86775ad9 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -352,7 +352,6 @@ export class GraphQLApi extends Construct { } private schemaMode: SchemaDefinition; - private types: ObjectType[]; private api: CfnGraphQLApi; private _apiKey?: string; @@ -411,7 +410,6 @@ export class GraphQLApi extends Construct { this.graphQlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; this.schemaMode = props.schemaDefinition; - this.types = []; if ( defaultAuthorizationType === AuthorizationType.API_KEY || @@ -736,7 +734,6 @@ export class GraphQLApi extends Construct { definition: props.definition, directives: props.directives, }); - this.types.push(type); this.generateObjectType(type); return type; } From 2a7c6415d77bc6dea5f24d1b89322eb8b76f1344 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:37:18 -0700 Subject: [PATCH 15/78] remove props --- packages/@aws-cdk/aws-appsync/lib/schema-types.ts | 4 ++-- packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index f5a8f2672b5bc..31a1521a88640 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -88,11 +88,11 @@ export class ObjectType { this.definition = props.definition; this.directives = props?.directives; } - } /** - * The Scalar Types in AppSync's GraphQL + * The Attribute Types in AppSync's GraphQL. Attribute Types are the + * building blocks for object types, queries, mutations, etc. */ export class AttributeType { /** diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 25f3ad057a8ea..0891996aa00fe 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -18,10 +18,10 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { api.addType('test', { definition: [ - appsync.ScalarType.string('id', { + appsync.AttributeType.string('id', { isRequired: true, }), - appsync.ScalarType.string('version', { + appsync.AttributeType.string('version', { isRequired: true, }), ], From 076ebdfb7434cf9cf502c6e4073e0e32ac40ab47 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:43:46 -0700 Subject: [PATCH 16/78] expected json file --- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 105 +++++++++--------- .../aws-appsync/test/appsync-schema.test.ts | 2 +- .../test/integ.graphql-schema.expected.json | 35 ++++++ .../aws-appsync/test/integ.graphql-schema.ts | 8 +- 4 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 31a1521a88640..f6f8a7b6329af 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -29,25 +29,6 @@ export class Directive { private constructor(statement: string) { this.statement = statement; } } -/** - * Properties of a GraphQL Schema Type - */ -export interface BaseTypeProps { - /** - * property declaring if this type is a list - * - * @default - false - */ - readonly isList?: boolean; - - /** - * property declaring if this type is required - * - * @default - false - */ - readonly isRequired?: boolean; -} - /** * Properties for configuring an Object Type */ @@ -100,32 +81,32 @@ export class AttributeType { * * Often used as a key for a cache and not intended to be human-readable. */ - public static id(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.ID, name, props); + public static id(name: string): AttributeType { + return new AttributeType(Type.ID, name); } /** * `String` scalar type is a free-form human-readable text. */ - public static string(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.STRING, name, props); + public static string(name: string): AttributeType { + return new AttributeType(Type.STRING, name); } /** * `Int` scalar type is a signed non-fractional numerical value. */ - public static int(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.INT, name, props); + public static int(name: string): AttributeType { + return new AttributeType(Type.INT, name); } /** * `Float` scalar type is a signed double-precision fractional value. */ - public static float(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.FLOAT, name, props); + public static float(name: string): AttributeType { + return new AttributeType(Type.FLOAT, name); } /** * `Boolean` scalar type is a boolean value: true or false. */ - public static boolean(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.BOOLEAN, name, props); + public static boolean(name: string): AttributeType { + return new AttributeType(Type.BOOLEAN, name); } /** @@ -135,8 +116,8 @@ export class AttributeType { * * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates */ - public static awsDate(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_DATE, name, props); + public static awsDate(name: string): AttributeType { + return new AttributeType(Type.AWS_DATE, name); } /** * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. @@ -145,8 +126,8 @@ export class AttributeType { * * @see https://en.wikipedia.org/wiki/ISO_8601#Times */ - public static awsTime(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_TIME, name, props); + public static awsTime(name: string): AttributeType { + return new AttributeType(Type.AWS_TIME, name); } /** * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. @@ -155,57 +136,57 @@ export class AttributeType { * * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations */ - public static awsDateTime(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_DATE_TIME, name, props); + public static awsDateTime(name: string): AttributeType { + return new AttributeType(Type.AWS_DATE_TIME, name); } /** * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. * * Timestamps are serialized and deserialized as numbers. */ - public static awsTimestamp(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_TIMESTAMP, name, props); + public static awsTimestamp(name: string): AttributeType { + return new AttributeType(Type.AWS_TIMESTAMP, name); } /** * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) */ - public static awsEmail(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_EMAIL, name, props); + public static awsEmail(name: string): AttributeType { + return new AttributeType(Type.AWS_EMAIL, name); } /** * `AWSJson` scalar type represents a JSON string. */ - public static awsJSON(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_JSON, name, props); + public static awsJSON(name: string): AttributeType { + return new AttributeType(Type.AWS_JSON, name); } /** * `AWSURL` scalar type represetns a valid URL string. * * URLs wihtout schemes or contain double slashes are considered invalid. */ - public static awsURL(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_URL, name, props); + public static awsURL(name: string): AttributeType { + return new AttributeType(Type.AWS_URL, name); } /** * `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. */ - public static awsPhone(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_PHONE, name, props); + public static awsPhone(name: string): AttributeType { + return new AttributeType(Type.AWS_PHONE, name); } /** * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. */ - public static awsIpAddress(name: string, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.AWS_IP_ADDRESS, name, props); + public static awsIpAddress(name: string): AttributeType { + return new AttributeType(Type.AWS_IP_ADDRESS, name); } /** * an object type to be added as an attribute */ - public static object(type: ObjectType, props?: BaseTypeProps): AttributeType { - return new AttributeType(Type.OBJECT, type.name, props); + public static object(type: ObjectType): AttributeType { + return new AttributeType(Type.OBJECT, type.name); } /** @@ -219,17 +200,33 @@ export class AttributeType { /** * property determining if this attribute is a list */ - public readonly isList: boolean; + public isList: boolean; /** * property determining if this attribute is required */ - public readonly isRequired: boolean; + public isRequired: boolean; - private constructor(type: Type, name: string, props?: BaseTypeProps) { + private constructor(type: Type, name: string) { this.type = type; this.name = name; - this.isList = props?.isList ?? false; - this.isRequired = props?.isRequired ?? false; + this.isList = false; + this.isRequired = false; + } + + /** + * Set this attribute type as a list + */ + public list(): AttributeType { + this.isList = true; + return this; + } + + /** + * Set this attribute type to be required + */ + public required(): AttributeType { + this.isRequired = true; + return this; } } 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 d031dbf77d542..8c2380cb30485 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -85,7 +85,7 @@ describe('testing schema definition mode `file`', () => { const when = () => { api.updateDefinition('error'); }; //THEN - expect(when).toThrowError('API cannot add type because schema definition mode is not configured as CODE.'); + expect(when).toThrowError('API cannot update schema because schema definition mode is not configured as CODE.'); }); test('definition mode `file` errors when schemaDefinitionFile is not configured', () => { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json new file mode 100644 index 0000000000000..d8e221279f6a7 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json @@ -0,0 +1,35 @@ +{ + "Resources": { + "codefirstapi1A3CC7D2": { + "Type": "AWS::AppSync::GraphQLApi", + "Properties": { + "AuthenticationType": "API_KEY", + "Name": "api" + } + }, + "codefirstapiDefaultAPIKeyApiKeyB73DF94B": { + "Type": "AWS::AppSync::ApiKey", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "Description": "Default API Key created by CDK" + } + }, + "codefirstapiSchema148B6CDE": { + "Type": "AWS::AppSync::GraphQLSchema", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "Definition": "type test @aws_iam {\n id: String!\n version: String!\n}\n\ntype Query {\n getTest(id: String!): test\n}\n" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 0891996aa00fe..3c43789ee2217 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -18,12 +18,8 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { api.addType('test', { definition: [ - appsync.AttributeType.string('id', { - isRequired: true, - }), - appsync.AttributeType.string('version', { - isRequired: true, - }), + appsync.AttributeType.string('id').required(), + appsync.AttributeType.string('version').required(), ], directives: [ appsync.Directive.iam(), From a351a0433d8693beb96e10b311015855436287e7 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 14:58:42 -0700 Subject: [PATCH 17/78] listing implementation corrected --- packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 3c43789ee2217..3b00279d933d6 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -26,6 +26,6 @@ api.addType('test', { ], }); -api.appendToSchema('type Query {\n getTest(id: String!): test\n}', '\n'); +api.appendToSchema('type Query {\n getTests(): test[]\n}', '\n'); app.synth(); \ No newline at end of file From 8637009eba1adad17ea1c4745ad6c38b097f7979 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 15:03:51 -0700 Subject: [PATCH 18/78] update readme --- packages/@aws-cdk/aws-appsync/README.md | 59 ++++++++++++++++++- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 6 +- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 40064e0822e02..7176800498d65 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -63,7 +63,7 @@ const demoTable = new db.Table(stack, 'DemoTable', { }, }); -const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos"', demoTable); +const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos', demoTable); // Resolver for the Query "getDemos" that scans the DyanmoDb table and returns the entire list. demoDS.createResolver({ @@ -159,4 +159,59 @@ 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 +``` + +### Code-First Schema + +CDK offers the ability to generate your schema in a code-first approach. +A code-first approach offers a developer workflow with: +- **modularity**: organizing schema type definitions into different files +- **reusability**: simplifying down boilerplate/repetitive code +- **consistency**: resolvers and schema definition will always be synced + +#### Code-First Example + +We are going to reference the [example](#Example) through a code-first approach. + +```ts +import * as appsync from '@aws-cdk/aws-appsync'; +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: { + authorizationType: appsync.AuthorizationType.IAM + }, + }, +}); + +const demoTable = new db.Table(stack, 'DemoTable', { + partitionKey: { + name: 'id', + type: AttributeType.STRING, + }, +}); + +const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos', demoTable); + +// Schema Definition starts here + +const demo = api.addType('demo', { + definition: [ + appsync.AttributeType.string('id').required(), + appsync.AttributeType.string('version').required(), + ], +}); + +``` + +#### Attribute Types + +Attribute Types are the building blocks of types, whether they are object, +queries, mutations, etc. Attribute Types can be: +- [**Scalar Types**](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html): Id, Int, String, AWSDate, etc. +- **Object Types**: types that you generate (i.e. `demo` from the example above) + diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index f738b86775ad9..3fc2fed3b44d0 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -773,8 +773,10 @@ export class GraphQLApi extends Construct { * @param attribute - the attribute of a type */ private generateAttribute(attribute: AttributeType): string { - const list = attribute.isList ? '[]' : ''; + const listify = (type: string) => { + return attribute.isList ? `[${type}]` : type; + }; const required = attribute.isRequired ? '!' : ''; - return `${attribute.name}: ${attribute.type}${list}${required}`; + return `${attribute.name}: ${listify(attribute.type)}${required}`; } } From 0ecd95f863960b7d252825b55ccd736f831c26e2 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 15:12:00 -0700 Subject: [PATCH 19/78] fix bug in integ --- packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 3b00279d933d6..f7747e398a169 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -26,6 +26,6 @@ api.addType('test', { ], }); -api.appendToSchema('type Query {\n getTests(): test[]\n}', '\n'); +api.appendToSchema('type Query {\n getTests(): [test]\n}', '\n'); app.synth(); \ No newline at end of file From 7005315d3ba5f5b74cae2159ffcb7479ac962b3a Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 15:21:32 -0700 Subject: [PATCH 20/78] pls fix build bug --- .../aws-appsync/test/integ.graphql-schema.expected.json | 2 +- packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json index d8e221279f6a7..dc866b4a8e4d0 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json @@ -28,7 +28,7 @@ "ApiId" ] }, - "Definition": "type test @aws_iam {\n id: String!\n version: String!\n}\n\ntype Query {\n getTest(id: String!): test\n}\n" + "Definition": "type test @aws_iam {\n id: String!\n version: String!\n}\n\ntype Query {\n getTests: [test]\n}\n" } } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index f7747e398a169..bbe69437e92a1 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -26,6 +26,6 @@ api.addType('test', { ], }); -api.appendToSchema('type Query {\n getTests(): [test]\n}', '\n'); +api.appendToSchema('type Query {\n getTests: [test]\n}', '\n'); app.synth(); \ No newline at end of file From 28f7e939d6c90e4b6551a9f6bc80f187dacecf14 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 3 Aug 2020 15:29:03 -0700 Subject: [PATCH 21/78] add todo --- packages/@aws-cdk/aws-appsync/lib/schema-types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index f6f8a7b6329af..5db37a8ba6514 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -215,6 +215,8 @@ export class AttributeType { /** * Set this attribute type as a list + * + * // TODO: makeList or some alternative */ public list(): AttributeType { this.isList = true; From fc9856645218cb0e98df6ed8c0efb8fb594f1b38 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 4 Aug 2020 12:15:02 -0700 Subject: [PATCH 22/78] dont change list or required --- packages/@aws-cdk/aws-appsync/lib/schema-types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 5db37a8ba6514..f6f8a7b6329af 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -215,8 +215,6 @@ export class AttributeType { /** * Set this attribute type as a list - * - * // TODO: makeList or some alternative */ public list(): AttributeType { this.isList = true; From c27a1580c7b13944e4bb8d368faa1a6f08f218b3 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 5 Aug 2020 15:13:13 -0700 Subject: [PATCH 23/78] prelim testing for the OO design --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 14 +++-- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 23 ++++++- .../aws-appsync/test/generated.graphql | 33 ++++++++++ .../aws-appsync/test/integ.graphql-schema.ts | 60 +++++++++++++++++-- 4 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/generated.graphql diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 3fc2fed3b44d0..9af83be5c7061 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,4 +1,4 @@ -import { readFileSync } from 'fs'; +import { readFileSync, writeFile } from 'fs'; import { IUserPool } from '@aws-cdk/aws-cognito'; import { ITable } from '@aws-cdk/aws-dynamodb'; import { Grant, IGrantable, ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; @@ -718,6 +718,11 @@ export class GraphQLApi extends Construct { } const sep = delimiter ?? ''; this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; + writeFile('generated.graphql', this.schema.definition, (err) => { + if (err) { + throw new Error(err.message); + } + }); } /** @@ -773,10 +778,9 @@ export class GraphQLApi extends Construct { * @param attribute - the attribute of a type */ private generateAttribute(attribute: AttributeType): string { - const listify = (type: string) => { - return attribute.isList ? `[${type}]` : type; - }; + let type = attribute.objectType ? attribute.objectType?.name : attribute.type; + type = attribute.isList ? `[${type}]` : type; const required = attribute.isRequired ? '!' : ''; - return `${attribute.name}: ${listify(attribute.type)}${required}`; + return `${attribute.name}: ${type}${required}`; } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index f6f8a7b6329af..550cfc975ca7e 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -49,6 +49,16 @@ export interface ObjectTypeProps { * Object Types must be configured using addType to be added to schema */ export class ObjectType { + /** + * A method to define Object Types from an interface + * TODO: implement interface class + */ + public static extendInterface(name: string, typeInterface: ObjectType, props: ObjectTypeProps): ObjectType { + // check to make sure interface is properly scoped out + typeInterface; + return new ObjectType(name, props); + } + /** * the name of this object type */ @@ -185,8 +195,8 @@ export class AttributeType { /** * an object type to be added as an attribute */ - public static object(type: ObjectType): AttributeType { - return new AttributeType(Type.OBJECT, type.name); + public static object(name: string, type: ObjectType): AttributeType { + return new AttributeType(Type.OBJECT, name, type); } /** @@ -205,12 +215,19 @@ export class AttributeType { * property determining if this attribute is required */ public isRequired: boolean; + /** + * the object type linked to this attribute + * @default - no object type + */ + public objectType?: ObjectType; + - private constructor(type: Type, name: string) { + private constructor(type: Type, name: string, object?: ObjectType) { this.type = type; this.name = name; this.isList = false; this.isRequired = false; + this.objectType = object; } /** diff --git a/packages/@aws-cdk/aws-appsync/test/generated.graphql b/packages/@aws-cdk/aws-appsync/test/generated.graphql new file mode 100644 index 0000000000000..cbd7594bb92ce --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/generated.graphql @@ -0,0 +1,33 @@ +type Planet { + name: String + diameter: Int + rotationPeriod: Int + orbitalPeriod: Int + gravity: String + population: Float + climates: [String] + terrains: [String] + surfaceWater: Float + created: String + edited: String + id: ID! +} +type Species { + name: String + classification: String + designation: String + averageHeight: Float + averageLifespan: Int + eyeColors: [String] + hairColors: [String] + skinColors: [String] + language: String + homeworld: Planet + created: String + edited: String + id: ID! +} + +type Query { + getTests: [test] +} diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index bbe69437e92a1..567b9df8cf961 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -16,13 +16,63 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { schemaDefinition: appsync.SchemaDefinition.CODE, }); -api.addType('test', { +const t_id = appsync.AttributeType.id('id').required(); + +// Planet Props +const t_name = appsync.AttributeType.string('name'); +const t_diameter = appsync.AttributeType.int('diameter'); +const t_rotationPeriod = appsync.AttributeType.int('rotationPeriod'); +const t_orbitalPeriod = appsync.AttributeType.int('orbitalPeriod'); +const t_gravity = appsync.AttributeType.string('gravity'); +const t_population = appsync.AttributeType.float('population'); +const t_climates= appsync.AttributeType.string('climates').list(); +const t_terrains = appsync.AttributeType.string('terrains').list(); +const t_surfaceWater = appsync.AttributeType.float('surfaceWater'); +const t_created = appsync.AttributeType.string('created'); +const t_edited = appsync.AttributeType.string('edited'); + +// Species Props +const t_classification = appsync.AttributeType.string('classification'); +const t_designation = appsync.AttributeType.string('designation'); +const t_averageHeight = appsync.AttributeType.float('averageHeight'); +const t_averageLifespan = appsync.AttributeType.int('averageLifespan'); +const t_eyeColors = appsync.AttributeType.string('eyeColors').list(); +const t_hairColors = appsync.AttributeType.string('hairColors').list(); +const t_skinColors = appsync.AttributeType.string('skinColors').list(); +const t_language = appsync.AttributeType.string('language'); + +const t_planet = api.addType('Planet', { definition: [ - appsync.AttributeType.string('id').required(), - appsync.AttributeType.string('version').required(), + t_name, + t_diameter, + t_rotationPeriod, + t_orbitalPeriod, + t_gravity, + t_population, + t_climates, + t_terrains, + t_surfaceWater, + t_created, + t_edited, + t_id, ], - directives: [ - appsync.Directive.iam(), +}); + +api.addType('Species', { + definition: [ + t_name, + t_classification, + t_designation, + t_averageHeight, + t_averageLifespan, + t_eyeColors, + t_hairColors, + t_skinColors, + t_language, + appsync.AttributeType.object('homeworld', t_planet), + t_created, + t_edited, + t_id, ], }); From 72d8be4eb70ebb4e24ca9a368f7f253fbb3fc49a Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 5 Aug 2020 17:21:36 -0700 Subject: [PATCH 24/78] new implementation --- .../aws-appsync/test/generated.graphql | 4 +- .../test/integ.graphql-schema.expected.json | 2 +- .../aws-appsync/test/integ.graphql-schema.ts | 93 ++++++++----------- 3 files changed, 40 insertions(+), 59 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/test/generated.graphql b/packages/@aws-cdk/aws-appsync/test/generated.graphql index cbd7594bb92ce..564ea84f1c9d0 100644 --- a/packages/@aws-cdk/aws-appsync/test/generated.graphql +++ b/packages/@aws-cdk/aws-appsync/test/generated.graphql @@ -4,7 +4,7 @@ type Planet { rotationPeriod: Int orbitalPeriod: Int gravity: String - population: Float + population: [String] climates: [String] terrains: [String] surfaceWater: Float @@ -29,5 +29,5 @@ type Species { } type Query { - getTests: [test] + getPlanets: [Planet] } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json index dc866b4a8e4d0..47a8ef174ac08 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json @@ -28,7 +28,7 @@ "ApiId" ] }, - "Definition": "type test @aws_iam {\n id: String!\n version: String!\n}\n\ntype Query {\n getTests: [test]\n}\n" + "Definition": "type Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\ntype Species {\n name: String\n classification: String\n designation: String\n averageHeight: Float\n averageLifespan: Int\n eyeColors: [String]\n hairColors: [String]\n skinColors: [String]\n language: String\n homeworld: Planet\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" } } } diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 567b9df8cf961..9918a85b287c6 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -16,66 +16,47 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { schemaDefinition: appsync.SchemaDefinition.CODE, }); -const t_id = appsync.AttributeType.id('id').required(); - -// Planet Props -const t_name = appsync.AttributeType.string('name'); -const t_diameter = appsync.AttributeType.int('diameter'); -const t_rotationPeriod = appsync.AttributeType.int('rotationPeriod'); -const t_orbitalPeriod = appsync.AttributeType.int('orbitalPeriod'); -const t_gravity = appsync.AttributeType.string('gravity'); -const t_population = appsync.AttributeType.float('population'); -const t_climates= appsync.AttributeType.string('climates').list(); -const t_terrains = appsync.AttributeType.string('terrains').list(); -const t_surfaceWater = appsync.AttributeType.float('surfaceWater'); -const t_created = appsync.AttributeType.string('created'); -const t_edited = appsync.AttributeType.string('edited'); - -// Species Props -const t_classification = appsync.AttributeType.string('classification'); -const t_designation = appsync.AttributeType.string('designation'); -const t_averageHeight = appsync.AttributeType.float('averageHeight'); -const t_averageLifespan = appsync.AttributeType.int('averageLifespan'); -const t_eyeColors = appsync.AttributeType.string('eyeColors').list(); -const t_hairColors = appsync.AttributeType.string('hairColors').list(); -const t_skinColors = appsync.AttributeType.string('skinColors').list(); -const t_language = appsync.AttributeType.string('language'); - -const t_planet = api.addType('Planet', { - definition: [ - t_name, - t_diameter, - t_rotationPeriod, - t_orbitalPeriod, - t_gravity, - t_population, - t_climates, - t_terrains, - t_surfaceWater, - t_created, - t_edited, - t_id, - ], +const t_id_r = appsync.AttributeType.id().required(); +const t_string = appsync.AttributeType.string(); +const t_int = appsync.AttributeType.int(); +const t_float = appsync.AttributeType.float(); +const t_string_l = appsync.AttributeType.string().list(); + +const planet = api.addType('Planet', { + definition: { + name: t_string, + diameter: t_int, + rotationPeriod: t_int, + orbitalPeriod: t_int, + gravity: t_string, + population: t_string_l, + climates: t_string_l, + terrains: t_string_l, + surfaceWater: t_float, + created: t_string, + edited: t_string, + id: t_id_r, + }, }); api.addType('Species', { - definition: [ - t_name, - t_classification, - t_designation, - t_averageHeight, - t_averageLifespan, - t_eyeColors, - t_hairColors, - t_skinColors, - t_language, - appsync.AttributeType.object('homeworld', t_planet), - t_created, - t_edited, - t_id, - ], + definition: { + name: t_string, + classification: t_string, + designation: t_string, + averageHeight: t_float, + averageLifespan: t_int, + eyeColors: t_string_l, + hairColors: t_string_l, + skinColors: t_string_l, + language: t_string, + homeworld: appsync.AttributeType.object(planet), + created: t_string, + edited: t_string, + id: t_id_r, + }, }); -api.appendToSchema('type Query {\n getTests: [test]\n}', '\n'); +api.appendToSchema('type Query {\n getPlanets: [Planet]\n}', '\n'); app.synth(); \ No newline at end of file From bff3e3bd35a1e5aaac7d86884651068097c083b8 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 6 Aug 2020 10:28:46 -0700 Subject: [PATCH 25/78] oops.. forgot to stage changes --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 12 ++-- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 71 +++++++++---------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 9af83be5c7061..85412a34c8aff 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -765,8 +765,9 @@ export class GraphQLApi extends Construct { private generateObjectType(type: ObjectType): void { const directives = this.generateDirectives(type.directives); let schemaAddition = `type ${type.name} ${directives}{\n`; - type.definition.map((def) => { - const attribute = this.generateAttribute(def); + Object.keys(type.definition).forEach( (key) => { + const def = type.definition[key]; + const attribute = this.generateAttribute(key, def); schemaAddition = `${schemaAddition} ${attribute}\n`; }); this.appendToSchema(`${schemaAddition}}`); @@ -777,10 +778,13 @@ export class GraphQLApi extends Construct { * * @param attribute - the attribute of a type */ - private generateAttribute(attribute: AttributeType): string { + private generateAttribute(name: string, attribute: AttributeType): string { + if (!attribute) { + throw new Error(`Key ${name} missing attribute.`); + } let type = attribute.objectType ? attribute.objectType?.name : attribute.type; type = attribute.isList ? `[${type}]` : type; const required = attribute.isRequired ? '!' : ''; - return `${attribute.name}: ${type}${required}`; + return `${name}: ${type}${required}`; } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 550cfc975ca7e..9e6f50d7639fb 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -36,7 +36,7 @@ export interface ObjectTypeProps { /** * the attributes of this object type */ - readonly definition: AttributeType[]; + readonly definition: { [key: string]: AttributeType }; /** * the directives for this object type * @@ -66,7 +66,7 @@ export class ObjectType { /** * the attributes of this object type */ - public readonly definition: AttributeType[]; + public readonly definition: { [key: string]: AttributeType }; /** * the directives for this object type * @@ -91,32 +91,32 @@ export class AttributeType { * * Often used as a key for a cache and not intended to be human-readable. */ - public static id(name: string): AttributeType { - return new AttributeType(Type.ID, name); + public static id(): AttributeType { + return new AttributeType(Type.ID); } /** * `String` scalar type is a free-form human-readable text. */ - public static string(name: string): AttributeType { - return new AttributeType(Type.STRING, name); + public static string(): AttributeType { + return new AttributeType(Type.STRING); } /** * `Int` scalar type is a signed non-fractional numerical value. */ - public static int(name: string): AttributeType { - return new AttributeType(Type.INT, name); + public static int(): AttributeType { + return new AttributeType(Type.INT); } /** * `Float` scalar type is a signed double-precision fractional value. */ - public static float(name: string): AttributeType { - return new AttributeType(Type.FLOAT, name); + public static float(): AttributeType { + return new AttributeType(Type.FLOAT); } /** * `Boolean` scalar type is a boolean value: true or false. */ - public static boolean(name: string): AttributeType { - return new AttributeType(Type.BOOLEAN, name); + public static boolean(): AttributeType { + return new AttributeType(Type.BOOLEAN); } /** @@ -126,8 +126,8 @@ export class AttributeType { * * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates */ - public static awsDate(name: string): AttributeType { - return new AttributeType(Type.AWS_DATE, name); + public static awsDate(): AttributeType { + return new AttributeType(Type.AWS_DATE); } /** * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. @@ -136,8 +136,8 @@ export class AttributeType { * * @see https://en.wikipedia.org/wiki/ISO_8601#Times */ - public static awsTime(name: string): AttributeType { - return new AttributeType(Type.AWS_TIME, name); + public static awsTime(): AttributeType { + return new AttributeType(Type.AWS_TIME); } /** * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. @@ -146,67 +146,63 @@ export class AttributeType { * * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations */ - public static awsDateTime(name: string): AttributeType { - return new AttributeType(Type.AWS_DATE_TIME, name); + public static awsDateTime(): AttributeType { + return new AttributeType(Type.AWS_DATE_TIME); } /** * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. * * Timestamps are serialized and deserialized as numbers. */ - public static awsTimestamp(name: string): AttributeType { - return new AttributeType(Type.AWS_TIMESTAMP, name); + public static awsTimestamp(): AttributeType { + return new AttributeType(Type.AWS_TIMESTAMP); } /** * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) */ - public static awsEmail(name: string): AttributeType { - return new AttributeType(Type.AWS_EMAIL, name); + public static awsEmail(): AttributeType { + return new AttributeType(Type.AWS_EMAIL); } /** * `AWSJson` scalar type represents a JSON string. */ - public static awsJSON(name: string): AttributeType { - return new AttributeType(Type.AWS_JSON, name); + public static awsJSON(): AttributeType { + return new AttributeType(Type.AWS_JSON); } /** * `AWSURL` scalar type represetns a valid URL string. * * URLs wihtout schemes or contain double slashes are considered invalid. */ - public static awsURL(name: string): AttributeType { - return new AttributeType(Type.AWS_URL, name); + public static awsURL(): AttributeType { + return new AttributeType(Type.AWS_URL); } /** * `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. */ - public static awsPhone(name: string): AttributeType { - return new AttributeType(Type.AWS_PHONE, name); + public static awsPhone(): AttributeType { + return new AttributeType(Type.AWS_PHONE); } /** * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. */ - public static awsIpAddress(name: string): AttributeType { - return new AttributeType(Type.AWS_IP_ADDRESS, name); + public static awsIpAddress(): AttributeType { + return new AttributeType(Type.AWS_IP_ADDRESS); } /** * an object type to be added as an attribute */ - public static object(name: string, type: ObjectType): AttributeType { - return new AttributeType(Type.OBJECT, name, type); + public static object(type: ObjectType): AttributeType { + return new AttributeType(Type.OBJECT, type); } /** * the type of attribute */ public readonly type: Type; - /** - * the name of this attribute type - */ - public readonly name: string; /** * property determining if this attribute is a list */ @@ -222,9 +218,8 @@ export class AttributeType { public objectType?: ObjectType; - private constructor(type: Type, name: string, object?: ObjectType) { + private constructor(type: Type, object?: ObjectType) { this.type = type; - this.name = name; this.isList = false; this.isRequired = false; this.objectType = object; From 47d9c899c77e4d1caba6234be556845ac04f8a1a Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 6 Aug 2020 12:09:08 -0700 Subject: [PATCH 26/78] revert changes to prevent merge conflicts --- packages/@aws-cdk/aws-appsync/README.md | 2 +- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 7176800498d65..f26955eb9df2e 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -63,7 +63,7 @@ const demoTable = new db.Table(stack, 'DemoTable', { }, }); -const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos', demoTable); +const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos"', demoTable); // Resolver for the Query "getDemos" that scans the DyanmoDb table and returns the entire list. demoDS.createResolver({ diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 85412a34c8aff..9c87bbcc11e19 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -447,7 +447,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, @@ -477,7 +481,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 1e2dd60a4c89c59b562adcaf9834ca18c156f640 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 7 Aug 2020 16:04:23 -0700 Subject: [PATCH 27/78] updated api for generating attribute types --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 65 +--- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 306 ++++++++++++++---- .../aws-appsync/test/appsync-schema.test.ts | 95 ++++-- .../aws-appsync/test/integ.graphql-schema.ts | 59 ++-- .../test/schema-type-defintions.ts | 34 ++ 5 files changed, 391 insertions(+), 168 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 9c87bbcc11e19..c43bc379ac309 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -6,7 +6,7 @@ import { IFunction } from '@aws-cdk/aws-lambda'; import { Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; -import { ObjectType, ObjectTypeProps, Directive, AttributeType } from './schema-types'; +import { ObjectType, ObjectTypeProps } from './schema-types'; /** * enum with all possible values for AppSync authorization type @@ -701,20 +701,8 @@ export class GraphQLApi extends Construct { } /** - * 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 update schema because schema definition mode is not configured as CODE.'); - } - this.schema.definition = definition; - } - - /** - * Escape hatch to append to Schema as desired + * Escape hatch to append to Schema as desired. Will always result + * in a newline. * * @param addition the addition to add to schema * @param delimiter the delimiter between schema and addition @@ -747,52 +735,7 @@ export class GraphQLApi extends Construct { definition: props.definition, directives: props.directives, }); - this.generateObjectType(type); + this.appendToSchema(type.toString()); return type; } - - /** - * Utility function to generate directives - * - * @param directives the directives of a given type - * @param delimiter the separator betweeen directives - * @default - ' ' - */ - private generateDirectives (directives?: Directive[], delimiter?: string): string{ - let schemaAddition = ''; - if (!directives){ return schemaAddition; } - directives.map((directive) => { - schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; - }); - return schemaAddition; - } - - /** - * Generate object type - */ - private generateObjectType(type: ObjectType): void { - const directives = this.generateDirectives(type.directives); - let schemaAddition = `type ${type.name} ${directives}{\n`; - Object.keys(type.definition).forEach( (key) => { - const def = type.definition[key]; - const attribute = this.generateAttribute(key, def); - schemaAddition = `${schemaAddition} ${attribute}\n`; - }); - this.appendToSchema(`${schemaAddition}}`); - } - - /** - * Generate string for attributes - * - * @param attribute - the attribute of a type - */ - private generateAttribute(name: string, attribute: AttributeType): string { - if (!attribute) { - throw new Error(`Key ${name} missing attribute.`); - } - let type = attribute.objectType ? attribute.objectType?.name : attribute.type; - type = attribute.isList ? `[${type}]` : type; - const required = attribute.isRequired ? '!' : ''; - return `${name}: ${type}${required}`; - } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 9e6f50d7639fb..0416334e633f1 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -79,44 +79,170 @@ export class ObjectType { this.definition = props.definition; this.directives = props?.directives; } + + /** + * Create an Attribute Type representing this Object Type + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public attribute(options?: BaseAttributeTypeOptions): AttributeType{ + return AttributeType.object({ + isList: options?.isList, + isRequired: options?.isRequired, + isRequiredList: options?.isRequiredList, + objectType: this, + }); + } + + /** + * Generate the string of this object type + */ + public toString(): string { + const directives = this.generateDirectives(this.directives); + let schemaAddition = `type ${this.name} ${directives}{\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } + + /** + * Utility function to generate directives + * + * @param directives the directives of a given type + * @param delimiter the separator betweeen directives + * @default - ' ' + */ + private generateDirectives (directives?: Directive[], delimiter?: string): string{ + let schemaAddition = ''; + if (!directives){ return schemaAddition; } + directives.map((directive) => { + schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; + }); + return schemaAddition; + } +} + +/** + * Base options for Attribute Types + * + * @option isList - is this attribute a list + * @option isRequired - is this attribute non-nullable + * @option isRequiredList - is this attribute a non-nullable list + */ +export interface BaseAttributeTypeOptions { + /** + * property determining if this attribute is a list + * i.e. if true, attribute would be [Type] + * + * @default - false + */ + readonly isList?: boolean; + + /** + * property determining if this attribute is non-nullable + * i.e. if true, attribute would be Type! + * + * @default - false + */ + readonly isRequired?: boolean; + + /** + * property determining if this attribute is a non-nullable list + * i.e. if true, attribute would be [ Type ]! + * or if isRequired true, attribe would be [ Type! ]! + * + * @default - false + */ + readonly isRequiredList?: boolean; +} + +/** + * Options for Attribute Types + * + * @option isList - is this attribute a list + * @option isRequired - is this attribute non-nullable + * @option isRequiredList - is this attribute a non-nullable list + * @option objectType - the object type linked to this attribute + */ +export interface AttributeTypeOptions extends BaseAttributeTypeOptions { + /** + * the object type linked to this attribute + * @default - no object type + */ + readonly objectType?: ObjectType; } /** * The Attribute Types in AppSync's GraphQL. Attribute Types are the - * building blocks for object types, queries, mutations, etc. + * building blocks for object types, queries, mutations, etc. They are + * types like String, Int, Id or even Object Types you create. + * + * i.e. `String`, `String!`, `[String]`, `[String!]`, `[String]!` + * + * Attribute Types are used to define the entirety of schema. */ export class AttributeType { /** * `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. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static id(): AttributeType { - return new AttributeType(Type.ID); + public static id(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.ID, options); } /** * `String` scalar type is a free-form human-readable text. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static string(): AttributeType { - return new AttributeType(Type.STRING); + public static string(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.STRING, options); } /** * `Int` scalar type is a signed non-fractional numerical value. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static int(): AttributeType { - return new AttributeType(Type.INT); + public static int(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.INT, options); } /** * `Float` scalar type is a signed double-precision fractional value. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static float(): AttributeType { - return new AttributeType(Type.FLOAT); + public static float(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.FLOAT, options); } /** * `Boolean` scalar type is a boolean value: true or false. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static boolean(): AttributeType { - return new AttributeType(Type.BOOLEAN); + public static boolean(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.BOOLEAN, options); } /** @@ -124,121 +250,187 @@ export class AttributeType { * * 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 + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsDate(): AttributeType { - return new AttributeType(Type.AWS_DATE); + public static awsDate(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_DATE, options); } /** * `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 + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsTime(): AttributeType { - return new AttributeType(Type.AWS_TIME); + public static awsTime(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_TIME, options); } /** * `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 + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsDateTime(): AttributeType { - return new AttributeType(Type.AWS_DATE_TIME); + public static awsDateTime(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_DATE_TIME, options); } /** * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. * * Timestamps are serialized and deserialized as numbers. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsTimestamp(): AttributeType { - return new AttributeType(Type.AWS_TIMESTAMP); + public static awsTimestamp(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_TIMESTAMP, options); } /** * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsEmail(): AttributeType { - return new AttributeType(Type.AWS_EMAIL); + public static awsEmail(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_EMAIL, options); } /** * `AWSJson` scalar type represents a JSON string. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsJSON(): AttributeType { - return new AttributeType(Type.AWS_JSON); + public static awsJSON(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_JSON, options); } /** * `AWSURL` scalar type represetns a valid URL string. * * URLs wihtout schemes or contain double slashes are considered invalid. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsURL(): AttributeType { - return new AttributeType(Type.AWS_URL); + public static awsURL(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_URL, options); } /** * `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. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsPhone(): AttributeType { - return new AttributeType(Type.AWS_PHONE); + public static awsPhone(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_PHONE, options); } /** * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - public static awsIpAddress(): AttributeType { - return new AttributeType(Type.AWS_IP_ADDRESS); + public static awsIpAddress(options?: BaseAttributeTypeOptions): AttributeType { + return new AttributeType(Type.AWS_IP_ADDRESS, options); } /** * an object type to be added as an attribute - */ - public static object(type: ObjectType): AttributeType { - return new AttributeType(Type.OBJECT, type); + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + * - objectType + */ + public static object(options?: AttributeTypeOptions): AttributeType { + if (!options?.objectType) { + throw new Error('Attribute Type of object must be configured with corresponding Object Type'); + } + return new AttributeType(Type.OBJECT, options); } /** * the type of attribute */ public readonly type: Type; + /** * property determining if this attribute is a list + * i.e. if true, attribute would be `[Type]` + * + * @default - false */ - public isList: boolean; + public readonly isList: boolean; + /** - * property determining if this attribute is required + * property determining if this attribute is non-nullable + * i.e. if true, attribute would be `Type!` and this attribute + * must always have a value + * + * @default - false */ - public isRequired: boolean; + public readonly isRequired: boolean; + + /** + * property determining if this attribute is a non-nullable list + * i.e. if true, attribute would be `[ Type ]!` and this attribute's + * list must always have a value + * + * @default - false + */ + public readonly isRequiredList: boolean; + /** * the object type linked to this attribute * @default - no object type */ - public objectType?: ObjectType; + public readonly objectType?: ObjectType; - - private constructor(type: Type, object?: ObjectType) { + private constructor(type: Type, options?: AttributeTypeOptions) { this.type = type; - this.isList = false; - this.isRequired = false; - this.objectType = object; - } - - /** - * Set this attribute type as a list - */ - public list(): AttributeType { - this.isList = true; - return this; + this.isList = options?.isList ?? false; + this.isRequired = options?.isRequired ?? false; + this.isRequiredList = options?.isRequiredList ?? false; + this.objectType = options?.objectType; } /** - * Set this attribute type to be required - */ - public required(): AttributeType { - this.isRequired = true; - return this; + * Generate the string for this attribute + */ + public toString(): string{ + // If an Object Type, we use the name of the Object Type + let type = this.objectType ? this.objectType?.name : this.type; + // If configured as required, the Attribute Type becomes required + type = this.isRequired ? `${type}!` : type; + // If configured with isXxxList, the Attribute Type becomes a list + type = this.isList || this.isRequiredList ? `[${type}]` : type; + // If configured with isRequiredList, the list becomes required + type = this.isRequiredList ? `${type}!` : type; + return type; } } 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 8c2380cb30485..cfa9151aa3c9d 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -2,6 +2,7 @@ import { join } from 'path'; import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; +import * as t from './schema-type-defintions'; // Schema Definitions const type = 'type test {\n version: String!\n}\n\n'; @@ -14,7 +15,7 @@ beforeEach(() => { stack = new cdk.Stack(); }); -describe('testing schema definition mode `code`', () => { +describe('basic testing schema definition mode `code`', () => { test('definition mode `code` produces empty schema definition', () => { // WHEN @@ -23,23 +24,25 @@ describe('testing schema definition mode `code`', () => { schemaDefinition: appsync.SchemaDefinition.CODE, }); - //THEN + // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { Definition: '', }); }); - test('definition mode `code` generates correct schema with updateDefinition', () => { + test('definition mode `code` generates correct schema with appendToSchema', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schemaDefinition: appsync.SchemaDefinition.CODE, }); - api.updateDefinition(`${type}${query}${mutation}`); + api.appendToSchema(type); + api.appendToSchema(query); + api.appendToSchema(mutation); - //THEN + // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${type}${query}${mutation}`, + Definition: `${type}\n${query}\n${mutation}\n`, }); }); @@ -53,12 +56,45 @@ describe('testing schema definition mode `code`', () => { }); }; - //THEN + // THEN expect(when).toThrowError('definition mode CODE is incompatible with file definition. Change mode to FILE/S3 or unconfigure schemaDefinitionFile'); }); }); +describe('testing addType for schema definition mode `code`', () => { + let api: appsync.GraphQLApi; + beforeEach(() => { + // GIVEN + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); + }); + + test('addType', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + dupid: t.dup_id, + }, + }); + + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + +}); + describe('testing schema definition mode `file`', () => { test('definition mode `file` produces correct output', () => { @@ -69,36 +105,57 @@ describe('testing schema definition mode `file`', () => { schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), }); - //THEN + // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { Definition: `${type}${query}${mutation}`, }); }); - test('definition mode `file` errors when calling updateDefiniton function', () => { + 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.'); + }); + + test('definition mode `file` errors when addType is called', () => { // 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 update schema because schema definition mode is not configured as CODE.'); + const when = () => { + api.addType('blah', { + definition: { fail: t.id }, + }); + }; + + // 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', () => { + test('definition mode `file` errors when appendToSchema is called', () => { // WHEN + const api = new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.FILE, + schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), + }); + const when = () => { - new appsync.GraphQLApi(stack, 'API', { - name: 'demo', - schemaDefinition: appsync.SchemaDefinition.FILE, - }); + api.appendToSchema('blah'); }; - //THEN - expect(when).toThrowError('schemaDefinitionFile must be configured if using FILE definition mode.'); + // THEN + expect(when).toThrowError('API cannot append to schema because schema definition mode is not configured as CODE.'); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 9918a85b287c6..54e248828dfa2 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -1,5 +1,6 @@ import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; +import * as t from './schema-type-defintions'; /* * Creates an Appsync GraphQL API and schema in a code-first approach. @@ -16,44 +17,40 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { schemaDefinition: appsync.SchemaDefinition.CODE, }); -const t_id_r = appsync.AttributeType.id().required(); -const t_string = appsync.AttributeType.string(); -const t_int = appsync.AttributeType.int(); -const t_float = appsync.AttributeType.float(); -const t_string_l = appsync.AttributeType.string().list(); - const planet = api.addType('Planet', { definition: { - name: t_string, - diameter: t_int, - rotationPeriod: t_int, - orbitalPeriod: t_int, - gravity: t_string, - population: t_string_l, - climates: t_string_l, - terrains: t_string_l, - surfaceWater: t_float, - created: t_string, - edited: t_string, - id: t_id_r, + name: t.string, + diameter: t.int, + rotationPeriod: t.int, + orbitalPeriod: t.int, + gravity: t.string, + population: t.list_string, + climates: t.list_string, + terrains: t.list_string, + surfaceWater: t.float, + created: t.string, + edited: t.string, + id: t.required_id, }, }); +const t_planet = planet.attribute(); + api.addType('Species', { definition: { - name: t_string, - classification: t_string, - designation: t_string, - averageHeight: t_float, - averageLifespan: t_int, - eyeColors: t_string_l, - hairColors: t_string_l, - skinColors: t_string_l, - language: t_string, - homeworld: appsync.AttributeType.object(planet), - created: t_string, - edited: t_string, - id: t_id_r, + name: t.string, + classification: t.string, + designation: t.string, + averageHeight: t.float, + averageLifespan: t.int, + eyeColors: t.list_string, + hairColors: t.list_string, + skinColors: t.list_string, + language: t.string, + homeworld: t_planet, + created: t.string, + edited: t.string, + id: t.required_id, }, }); diff --git a/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts b/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts new file mode 100644 index 0000000000000..4bbdf23e48745 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts @@ -0,0 +1,34 @@ +import { AttributeType } from '../lib'; + +// STRING +export const string = AttributeType.string(); +export const list_string = AttributeType.string({ + isList: true, +}); + +// ID +export const id = AttributeType.id(); +export const list_id = AttributeType.id({ + isList: true, +}); +export const required_id = AttributeType.id({ + isRequired: true, +}); +export const required_list_id = AttributeType.id({ + isRequiredList: true, +}); +export const required_list_required_id = AttributeType.id({ + isRequired: true, + isRequiredList: true, +}); +export const dup_id = AttributeType.id({ + isList: true, + isRequired: true, + isRequiredList: true, +}); + +// INT +export const int = AttributeType.int(); + +//FLOAT +export const float = AttributeType.float(); From 1d1aec99c584051d6bb5dbbf196b8b494ef48c02 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 12:09:06 -0700 Subject: [PATCH 28/78] round off integ test, clean up writing, change api name --- packages/@aws-cdk/aws-appsync/README.md | 14 +- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 7 +- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 92 ++++++------ .../aws-appsync/test/generated.graphql | 33 ----- .../test/integ.graphql-schema.expected.json | 133 +++++++++++++++++- .../aws-appsync/test/integ.graphql-schema.ts | 40 +++--- .../test/schema-type-defintions.ts | 22 +-- .../test/verify.integ.graphql-schema.sh | 32 +++++ 8 files changed, 250 insertions(+), 123 deletions(-) delete mode 100644 packages/@aws-cdk/aws-appsync/test/generated.graphql create mode 100644 packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index f26955eb9df2e..dc3094d6b7c4b 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -59,7 +59,7 @@ const api = new appsync.GraphQLApi(stack, 'Api', { const demoTable = new db.Table(stack, 'DemoTable', { partitionKey: { name: 'id', - type: AttributeType.STRING, + type: db.AttributeType.STRING, }, }); @@ -191,7 +191,7 @@ const api = new appsync.GraphQLApi(stack, 'Api', { const demoTable = new db.Table(stack, 'DemoTable', { partitionKey: { name: 'id', - type: AttributeType.STRING, + type: db.AttributeType.STRING, }, }); @@ -201,17 +201,17 @@ const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos', de const demo = api.addType('demo', { definition: [ - appsync.AttributeType.string('id').required(), - appsync.AttributeType.string('version').required(), + appsync.GraphqlType.string('id').required(), + appsync.GraphqlType.string('version').required(), ], }); ``` -#### Attribute Types +#### GraphQL Types -Attribute Types are the building blocks of types, whether they are object, -queries, mutations, etc. Attribute Types can be: +GraphQL Types are the building blocks of types, whether they are object, +queries, mutations, etc. GraphQL Types can be: - [**Scalar Types**](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html): Id, Int, String, AWSDate, etc. - **Object Types**: types that you generate (i.e. `demo` from the example above) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index c43bc379ac309..8b556b2882b1e 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,4 +1,4 @@ -import { readFileSync, writeFile } from 'fs'; +import { readFileSync } from 'fs'; import { IUserPool } from '@aws-cdk/aws-cognito'; import { ITable } from '@aws-cdk/aws-dynamodb'; import { Grant, IGrantable, ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; @@ -714,11 +714,6 @@ export class GraphQLApi extends Construct { } const sep = delimiter ?? ''; this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; - writeFile('generated.graphql', this.schema.definition, (err) => { - if (err) { - throw new Error(err.message); - } - }); } /** diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 0416334e633f1..2879eaac353b5 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -36,7 +36,7 @@ export interface ObjectTypeProps { /** * the attributes of this object type */ - readonly definition: { [key: string]: AttributeType }; + readonly definition: { [key: string]: GraphqlType }; /** * the directives for this object type * @@ -66,7 +66,7 @@ export class ObjectType { /** * the attributes of this object type */ - public readonly definition: { [key: string]: AttributeType }; + public readonly definition: { [key: string]: GraphqlType }; /** * the directives for this object type * @@ -81,15 +81,15 @@ export class ObjectType { } /** - * Create an Attribute Type representing this Object Type + * Create an GraphQL Type representing this Object Type * * @param options the options to configure this attribute * - isList * - isRequired * - isRequiredList */ - public attribute(options?: BaseAttributeTypeOptions): AttributeType{ - return AttributeType.object({ + public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ + return GraphqlType.graphqlObject({ isList: options?.isList, isRequired: options?.isRequired, isRequiredList: options?.isRequiredList, @@ -128,13 +128,13 @@ export class ObjectType { } /** - * Base options for Attribute Types + * Base options for GraphQL Types * * @option isList - is this attribute a list * @option isRequired - is this attribute non-nullable * @option isRequiredList - is this attribute a non-nullable list */ -export interface BaseAttributeTypeOptions { +export interface BaseGraphqlTypeOptions { /** * property determining if this attribute is a list * i.e. if true, attribute would be [Type] @@ -162,14 +162,14 @@ export interface BaseAttributeTypeOptions { } /** - * Options for Attribute Types + * Options for GraphQL Types * * @option isList - is this attribute a list * @option isRequired - is this attribute non-nullable * @option isRequiredList - is this attribute a non-nullable list * @option objectType - the object type linked to this attribute */ -export interface AttributeTypeOptions extends BaseAttributeTypeOptions { +export interface GraphqlTypeOptions extends BaseGraphqlTypeOptions { /** * the object type linked to this attribute * @default - no object type @@ -178,15 +178,15 @@ export interface AttributeTypeOptions extends BaseAttributeTypeOptions { } /** - * The Attribute Types in AppSync's GraphQL. Attribute Types are the + * The GraphQL Types in AppSync's GraphQL. GraphQL Types are the * building blocks for object types, queries, mutations, etc. They are * types like String, Int, Id or even Object Types you create. * * i.e. `String`, `String!`, `[String]`, `[String!]`, `[String]!` * - * Attribute Types are used to define the entirety of schema. + * GraphQL Types are used to define the entirety of schema. */ -export class AttributeType { +export class GraphqlType { /** * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. * @@ -197,8 +197,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static id(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.ID, options); + public static id(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.ID, options); } /** * `String` scalar type is a free-form human-readable text. @@ -208,8 +208,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static string(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.STRING, options); + public static string(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.STRING, options); } /** * `Int` scalar type is a signed non-fractional numerical value. @@ -219,8 +219,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static int(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.INT, options); + public static int(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.INT, options); } /** * `Float` scalar type is a signed double-precision fractional value. @@ -230,8 +230,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static float(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.FLOAT, options); + public static float(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.FLOAT, options); } /** * `Boolean` scalar type is a boolean value: true or false. @@ -241,8 +241,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static boolean(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.BOOLEAN, options); + public static boolean(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.BOOLEAN, options); } /** @@ -255,8 +255,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsDate(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_DATE, options); + public static awsDate(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_DATE, options); } /** * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. @@ -268,8 +268,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsTime(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_TIME, options); + public static awsTime(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_TIME, options); } /** * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. @@ -281,8 +281,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsDateTime(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_DATE_TIME, options); + public static awsDateTime(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_DATE_TIME, options); } /** * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. @@ -294,8 +294,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsTimestamp(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_TIMESTAMP, options); + public static awsTimestamp(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_TIMESTAMP, options); } /** * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) @@ -305,8 +305,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsEmail(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_EMAIL, options); + public static awsEmail(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_EMAIL, options); } /** * `AWSJson` scalar type represents a JSON string. @@ -316,8 +316,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsJSON(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_JSON, options); + public static awsJSON(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_JSON, options); } /** * `AWSURL` scalar type represetns a valid URL string. @@ -329,8 +329,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsURL(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_URL, options); + public static awsURL(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_URL, options); } /** * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. @@ -342,8 +342,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsPhone(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_PHONE, options); + public static awsPhone(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_PHONE, options); } /** * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. @@ -353,8 +353,8 @@ export class AttributeType { * - isRequired * - isRequiredList */ - public static awsIpAddress(options?: BaseAttributeTypeOptions): AttributeType { - return new AttributeType(Type.AWS_IP_ADDRESS, options); + public static awsIpAddress(options?: BaseGraphqlTypeOptions): GraphqlType { + return new GraphqlType(Type.AWS_IP_ADDRESS, options); } /** @@ -366,11 +366,11 @@ export class AttributeType { * - isRequiredList * - objectType */ - public static object(options?: AttributeTypeOptions): AttributeType { + public static graphqlObject(options?: GraphqlTypeOptions): GraphqlType { if (!options?.objectType) { - throw new Error('Attribute Type of object must be configured with corresponding Object Type'); + throw new Error('GraphQL Type of object must be configured with corresponding Object Type'); } - return new AttributeType(Type.OBJECT, options); + return new GraphqlType(Type.OBJECT, options); } /** @@ -410,7 +410,7 @@ export class AttributeType { */ public readonly objectType?: ObjectType; - private constructor(type: Type, options?: AttributeTypeOptions) { + private constructor(type: Type, options?: GraphqlTypeOptions) { this.type = type; this.isList = options?.isList ?? false; this.isRequired = options?.isRequired ?? false; @@ -424,9 +424,9 @@ export class AttributeType { public toString(): string{ // If an Object Type, we use the name of the Object Type let type = this.objectType ? this.objectType?.name : this.type; - // If configured as required, the Attribute Type becomes required + // If configured as required, the GraphQL Type becomes required type = this.isRequired ? `${type}!` : type; - // If configured with isXxxList, the Attribute Type becomes a list + // If configured with isXxxList, the GraphQL Type becomes a list type = this.isList || this.isRequiredList ? `[${type}]` : type; // If configured with isRequiredList, the list becomes required type = this.isRequiredList ? `${type}!` : type; diff --git a/packages/@aws-cdk/aws-appsync/test/generated.graphql b/packages/@aws-cdk/aws-appsync/test/generated.graphql deleted file mode 100644 index 564ea84f1c9d0..0000000000000 --- a/packages/@aws-cdk/aws-appsync/test/generated.graphql +++ /dev/null @@ -1,33 +0,0 @@ -type Planet { - name: String - diameter: Int - rotationPeriod: Int - orbitalPeriod: Int - gravity: String - population: [String] - climates: [String] - terrains: [String] - surfaceWater: Float - created: String - edited: String - id: ID! -} -type Species { - name: String - classification: String - designation: String - averageHeight: Float - averageLifespan: Int - eyeColors: [String] - hairColors: [String] - skinColors: [String] - language: String - homeworld: Planet - created: String - edited: String - id: ID! -} - -type Query { - getPlanets: [Planet] -} diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json index 47a8ef174ac08..b7da6dd44c827 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json @@ -28,8 +28,139 @@ "ApiId" ] }, - "Definition": "type Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\ntype Species {\n name: String\n classification: String\n designation: String\n averageHeight: Float\n averageLifespan: Int\n eyeColors: [String]\n hairColors: [String]\n skinColors: [String]\n language: String\n homeworld: Planet\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" + "Definition": "type Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" } + }, + "codefirstapiplanetsDSServiceRoleCC9B598D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appsync.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "codefirstapiplanetsDSServiceRoleDefaultPolicy772878B1": { + "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": [ + "table8235A42E", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "codefirstapiplanetsDSServiceRoleDefaultPolicy772878B1", + "Roles": [ + { + "Ref": "codefirstapiplanetsDSServiceRoleCC9B598D" + } + ] + } + }, + "codefirstapiplanetsDSD0C53925": { + "Type": "AWS::AppSync::DataSource", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "Name": "planets", + "Type": "AMAZON_DYNAMODB", + "Description": "table for planets", + "DynamoDBConfig": { + "AwsRegion": { + "Ref": "AWS::Region" + }, + "TableName": { + "Ref": "table8235A42E" + } + }, + "ServiceRoleArn": { + "Fn::GetAtt": [ + "codefirstapiplanetsDSServiceRoleCC9B598D", + "Arn" + ] + } + } + }, + "codefirstapiplanetsDSQuerygetPlanetsResolver07AC768F": { + "Type": "AWS::AppSync::Resolver", + "Properties": { + "ApiId": { + "Fn::GetAtt": [ + "codefirstapi1A3CC7D2", + "ApiId" + ] + }, + "FieldName": "getPlanets", + "TypeName": "Query", + "DataSourceName": "planets", + "Kind": "UNIT", + "RequestMappingTemplate": "{\"version\" : \"2017-02-28\", \"operation\" : \"Scan\"}", + "ResponseMappingTemplate": "$util.toJson($ctx.result.items)" + }, + "DependsOn": [ + "codefirstapiplanetsDSD0C53925", + "codefirstapiSchema148B6CDE" + ] + }, + "table8235A42E": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 54e248828dfa2..86a3694605860 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -1,4 +1,5 @@ import * as cdk from '@aws-cdk/core'; +import * as db from '@aws-cdk/aws-dynamodb'; import * as appsync from '../lib'; import * as t from './schema-type-defintions'; @@ -6,9 +7,14 @@ import * as t from './schema-type-defintions'; * Creates an Appsync GraphQL API and schema in a code-first approach. * * Stack verification steps: + * Deploy stack, get api key and endpoint. Check if schema connects to data source. * + * -- bash verify.integ.graphql-schema.sh --start -- start -- + * -- aws appsync list-graphql-apis -- obtain apiId & endpoint -- + * -- aws appsync list-api-keys --api-id [apiId] -- obtain api key -- + * -- bash verify.integ.graphql-schema.sh --check [apiKey] [url] -- check if success -- + * -- bash verify.integ.graphql-schema.sh --clean -- clean -- */ - const app = new cdk.App(); const stack = new cdk.Stack(app, 'code-first-schema'); @@ -17,7 +23,7 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { schemaDefinition: appsync.SchemaDefinition.CODE, }); -const planet = api.addType('Planet', { +api.addType('Planet', { definition: { name: t.string, diameter: t.int, @@ -34,26 +40,22 @@ const planet = api.addType('Planet', { }, }); -const t_planet = planet.attribute(); +api.appendToSchema('type Query {\n getPlanets: [Planet]\n}', '\n'); -api.addType('Species', { - definition: { - name: t.string, - classification: t.string, - designation: t.string, - averageHeight: t.float, - averageLifespan: t.int, - eyeColors: t.list_string, - hairColors: t.list_string, - skinColors: t.list_string, - language: t.string, - homeworld: t_planet, - created: t.string, - edited: t.string, - id: t.required_id, +const table = new db.Table(stack, 'table', { + partitionKey: { + name: 'id', + type: db.AttributeType.STRING, }, }); -api.appendToSchema('type Query {\n getPlanets: [Planet]\n}', '\n'); +const tableDS = api.addDynamoDbDataSource('planets', 'table for planets', table); + +tableDS.createResolver({ + typeName: 'Query', + fieldName: 'getPlanets', + requestMappingTemplate: appsync.MappingTemplate.dynamoDbScanTable(), + responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(), +}); app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts b/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts index 4bbdf23e48745..b0c72834068b8 100644 --- a/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts +++ b/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts @@ -1,34 +1,34 @@ -import { AttributeType } from '../lib'; +import { GraphqlType } from '../lib'; // STRING -export const string = AttributeType.string(); -export const list_string = AttributeType.string({ +export const string = GraphqlType.string(); +export const list_string = GraphqlType.string({ isList: true, }); // ID -export const id = AttributeType.id(); -export const list_id = AttributeType.id({ +export const id = GraphqlType.id(); +export const list_id = GraphqlType.id({ isList: true, }); -export const required_id = AttributeType.id({ +export const required_id = GraphqlType.id({ isRequired: true, }); -export const required_list_id = AttributeType.id({ +export const required_list_id = GraphqlType.id({ isRequiredList: true, }); -export const required_list_required_id = AttributeType.id({ +export const required_list_required_id = GraphqlType.id({ isRequired: true, isRequiredList: true, }); -export const dup_id = AttributeType.id({ +export const dup_id = GraphqlType.id({ isList: true, isRequired: true, isRequiredList: true, }); // INT -export const int = AttributeType.int(); +export const int = GraphqlType.int(); //FLOAT -export const float = AttributeType.float(); +export const float = GraphqlType.float(); diff --git a/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh new file mode 100644 index 0000000000000..95662531fece5 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/verify.integ.graphql-schema.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +function error { + printf "\e[91;5;81m$@\e[0m\n" +} + +function usage { + echo "###############################################################################" + echo "# run 'verify.integ.graphql-schema.sh --start' to deploy #" + echo "# run 'verify.integ.graphql-schema.sh --check [APIKEY] [ENDPOINT]' to run check #" + echo "# run 'verify.integ.graphql-schema.sh --clean' to clean up stack #" + echo "###############################################################################" +} + +if [[ "$1" == "--start" ]]; then + cdk deploy --app "node integ.graphql-schema.js" +elif [[ "$1" == "--check" ]]; then + if [[ -z $2 || -z $3 ]]; then + error "Error: --check flag requires [APIKEY] [ENDPOINT]" + usage + exit 1 + fi + echo THIS TEST SHOULD SUCCEED + curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:$2" -d '{ "query": "query { getPlanets { id name } }" }" }' $3 + echo "" +elif [[ "$1" == "--clean" ]];then + cdk destroy --app "node integ.graphql-schema.js" +else + error "Error: use flags --start, --check, --clean" + usage + exit 1 +fi \ No newline at end of file From a1e34a3ecb3a5cbb7834492c4cc07dbd30b4761f Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 12:14:14 -0700 Subject: [PATCH 29/78] chore up readme --- packages/@aws-cdk/aws-appsync/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 6fff2c02c88d7..30586ab3fbd3d 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -201,18 +201,24 @@ const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos', de // Schema Definition starts here const demo = api.addType('demo', { - definition: [ - appsync.GraphqlType.string('id').required(), - appsync.GraphqlType.string('version').required(), - ], + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), + }, }); ``` #### GraphQL Types +One of the benefits of GraphQL is its strongly typed nature. We define the +types within an object, query, mutation, interface, etc. as **GraphQL Types**. + GraphQL Types are the building blocks of types, whether they are object, queries, mutations, etc. GraphQL Types can be: - [**Scalar Types**](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html): Id, Int, String, AWSDate, etc. - **Object Types**: types that you generate (i.e. `demo` from the example above) +More concretely, GraphQL Types are simply the types appended to variables. +Referencing the object type Author in the previous example, the GraphQL Types +is `String!` and is applied to both the names `id` and `version`. \ No newline at end of file From e7b57a4efcd3ac7534bad4b97969f96a212b4da0 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 12:40:30 -0700 Subject: [PATCH 30/78] chore up unit tests --- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 6 +- .../test/appsync-code-first.test.ts | 251 ++++++++++++++++++ .../aws-appsync/test/appsync-schema.test.ts | 33 --- .../test/schema-type-defintions.ts | 44 ++- 4 files changed, 291 insertions(+), 43 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 2879eaac353b5..916bb2b5a6eae 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -316,7 +316,7 @@ export class GraphqlType { * - isRequired * - isRequiredList */ - public static awsJSON(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsJson(options?: BaseGraphqlTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_JSON, options); } /** @@ -329,7 +329,7 @@ export class GraphqlType { * - isRequired * - isRequiredList */ - public static awsURL(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsUrl(options?: BaseGraphqlTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_URL, options); } /** @@ -504,7 +504,7 @@ export enum Type { * * URLs wihtout schemes or contain double slashes are considered invalid. */ - AWS_URL = 'AWSUrl', + AWS_URL = 'AWSURL', /** * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. * diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts new file mode 100644 index 0000000000000..6eb682cf75794 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -0,0 +1,251 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; +import * as t from './schema-type-defintions'; + +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); +}); + +describe('testing addType for schema definition mode `code`', () => { + test('check scalar type id with all options', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + dupid: t.dup_id, + }, + }); + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + +}); + +describe('testing all GraphQL Types', () => { + test('scalar type id', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.id, + }, + }); + const out = 'type Test {\n id: ID\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type string', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.string, + }, + }); + const out = 'type Test {\n id: String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type int', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.int, + }, + }); + const out = 'type Test {\n id: Int\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type float', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.float, + }, + }); + const out = 'type Test {\n id: Float\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type boolean', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.boolean, + }, + }); + const out = 'type Test {\n id: Boolean\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSDate', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsDate, + }, + }); + const out = 'type Test {\n id: AWSDate\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSTime', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsTime, + }, + }); + const out = 'type Test {\n id: AWSTime\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSDateTime', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsDateTime, + }, + }); + const out = 'type Test {\n id: AWSDateTime\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSTimestamp', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsTimestamp, + }, + }); + const out = 'type Test {\n id: AWSTimestamp\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSEmail', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsEmail, + }, + }); + const out = 'type Test {\n id: AWSEmail\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSJSON', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsJson, + }, + }); + const out = 'type Test {\n id: AWSJSON\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + + test('scalar type AWSUrl', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsUrl, + }, + }); + const out = 'type Test {\n id: AWSURL\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSPhone', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsPhone, + }, + }); + const out = 'type Test {\n id: AWSPhone\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSIPAddress', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsIpAddress, + }, + }); + const out = 'type Test {\n id: AWSIPAddress\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); \ No newline at end of file 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 cfa9151aa3c9d..0128ce6085e75 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -62,39 +62,6 @@ describe('basic testing schema definition mode `code`', () => { }); -describe('testing addType for schema definition mode `code`', () => { - let api: appsync.GraphQLApi; - beforeEach(() => { - // GIVEN - api = new appsync.GraphQLApi(stack, 'api', { - name: 'api', - schemaDefinition: appsync.SchemaDefinition.CODE, - }); - }); - - test('addType', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.id, - lid: t.list_id, - rid: t.required_id, - rlid: t.required_list_id, - rlrid: t.required_list_required_id, - dupid: t.dup_id, - }, - }); - - const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - -}); - describe('testing schema definition mode `file`', () => { test('definition mode `file` produces correct output', () => { diff --git a/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts b/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts index b0c72834068b8..e13df50ca0fc4 100644 --- a/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts +++ b/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts @@ -1,11 +1,5 @@ import { GraphqlType } from '../lib'; -// STRING -export const string = GraphqlType.string(); -export const list_string = GraphqlType.string({ - isList: true, -}); - // ID export const id = GraphqlType.id(); export const list_id = GraphqlType.id({ @@ -27,8 +21,44 @@ export const dup_id = GraphqlType.id({ isRequiredList: true, }); +// STRING +export const string = GraphqlType.string(); +export const list_string = GraphqlType.string({ + isList: true, +}); + // INT export const int = GraphqlType.int(); -//FLOAT +// FLOAT export const float = GraphqlType.float(); + +// BOOLEAN +export const boolean = GraphqlType.boolean(); + +// AWSDate +export const awsDate = GraphqlType.awsDate(); + +// AWSTime +export const awsTime = GraphqlType.awsTime(); + +// AWSDateTime +export const awsDateTime = GraphqlType.awsDateTime(); + +// AWSTimestamp +export const awsTimestamp = GraphqlType.awsTimestamp(); + +// AWSEmail +export const awsEmail = GraphqlType.awsEmail(); + +// AWSJSON +export const awsJson = GraphqlType.awsJson(); + +// AWSUrl +export const awsUrl = GraphqlType.awsUrl(); + +// AWSPhone +export const awsPhone = GraphqlType.awsPhone(); + +// AWSIPAddress +export const awsIpAddress = GraphqlType.awsIpAddress(); From 7c8115f54afc0b5940d5d14286fad9f712ec547f Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 12:59:41 -0700 Subject: [PATCH 31/78] allow for object types to be created externally --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 7 ++-- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 34 +++++++++++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 0f47c8f85053e..e4886a46e2e9b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -732,12 +732,11 @@ export class GraphQLApi extends Construct { public addType(name: string, props: ObjectTypeProps): ObjectType { if ( this.schemaMode != SchemaDefinition.CODE ) { throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); - } - const type = new ObjectType(name, { + }; + return new ObjectType(name, { + api: this, definition: props.definition, directives: props.directives, }); - this.appendToSchema(type.toString()); - return type; } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 916bb2b5a6eae..74d8188c5196d 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -1,3 +1,5 @@ +import { GraphQLApi } from './graphqlapi'; + /** * Directives for types * @@ -33,6 +35,12 @@ export class Directive { * Properties for configuring an Object Type */ export interface ObjectTypeProps { + /** + * the api linked to this object type + * + * @default - no linked GraphQL Api + */ + readonly api?: GraphQLApi; /** * the attributes of this object type */ @@ -63,6 +71,12 @@ export class ObjectType { * the name of this object type */ public readonly name: string; + /** + * the api linked to this object type + * + * @defualt - no linked GraphQL Api + */ + public readonly api?: GraphQLApi; /** * the attributes of this object type */ @@ -76,8 +90,13 @@ export class ObjectType { public constructor(name: string, props: ObjectTypeProps) { this.name = name; + this.api = props.api; this.definition = props.definition; - this.directives = props?.directives; + this.directives = props.directives; + + if (this.api) { + this.appendToSchema(this.api); + } } /** @@ -117,7 +136,7 @@ export class ObjectType { * @param delimiter the separator betweeen directives * @default - ' ' */ - private generateDirectives (directives?: Directive[], delimiter?: string): string{ + private generateDirectives(directives?: Directive[], delimiter?: string): string{ let schemaAddition = ''; if (!directives){ return schemaAddition; } directives.map((directive) => { @@ -125,6 +144,17 @@ export class ObjectType { }); return schemaAddition; } + + /** + * Add object type definition to a linked api. + * + * @param api - the GraphQL Api to append to + * @param delimiter the delimiter between schema and addition + * @default - '' + */ + public appendToSchema(api: GraphQLApi, delimiter?: string): void{ + api.appendToSchema(this.toString(), delimiter); + } } /** From 04705e4077efba25c3648bce7f169a196395d219 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 14:12:48 -0700 Subject: [PATCH 32/78] update integ for external object type --- .../test/integ.graphql-schema.expected.json | 2 +- .../aws-appsync/test/integ.graphql-schema.ts | 35 +++++++++++-------- .../test/object-type-definitions.ts | 19 ++++++++++ ...efintions.ts => scalar-type-defintions.ts} | 0 4 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts rename packages/@aws-cdk/aws-appsync/test/{schema-type-defintions.ts => scalar-type-defintions.ts} (100%) diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json index b7da6dd44c827..27a331a52ba65 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json @@ -28,7 +28,7 @@ "ApiId" ] }, - "Definition": "type Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" + "Definition": "type Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\ntype Species {\n name: String\n classification: String\n designation: String\n averageHeight: Float\n averageLifespan: Int\n eyeColors: [String]\n hairColors: [String]\n skinColors: [String]\n language: String\n homeworld: Planet\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" } }, "codefirstapiplanetsDSServiceRoleCC9B598D": { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 86a3694605860..bf8797e4c13b0 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -1,13 +1,14 @@ import * as cdk from '@aws-cdk/core'; import * as db from '@aws-cdk/aws-dynamodb'; import * as appsync from '../lib'; -import * as t from './schema-type-defintions'; +import * as ObjectType from './object-type-definitions'; +import * as ScalarType from './scalar-type-defintions'; /* * Creates an Appsync GraphQL API and schema in a code-first approach. * * Stack verification steps: - * Deploy stack, get api key and endpoint. Check if schema connects to data source. + * Deploy stack, get api key and endpoinScalarType. Check if schema connects to data source. * * -- bash verify.integ.graphql-schema.sh --start -- start -- * -- aws appsync list-graphql-apis -- obtain apiId & endpoint -- @@ -23,20 +24,24 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { schemaDefinition: appsync.SchemaDefinition.CODE, }); -api.addType('Planet', { +const planet = ObjectType.planet; +planet.appendToSchema(api); + +api.addType('Species', { definition: { - name: t.string, - diameter: t.int, - rotationPeriod: t.int, - orbitalPeriod: t.int, - gravity: t.string, - population: t.list_string, - climates: t.list_string, - terrains: t.list_string, - surfaceWater: t.float, - created: t.string, - edited: t.string, - id: t.required_id, + name: ScalarType.string, + classification: ScalarType.string, + designation: ScalarType.string, + averageHeight: ScalarType.float, + averageLifespan: ScalarType.int, + eyeColors: ScalarType.list_string, + hairColors: ScalarType.list_string, + skinColors: ScalarType.list_string, + language: ScalarType.string, + homeworld: planet.attribute(), + created: ScalarType.string, + edited: ScalarType.string, + id: ScalarType.required_id, }, }); diff --git a/packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts b/packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts new file mode 100644 index 0000000000000..138f2d7e1faa1 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/object-type-definitions.ts @@ -0,0 +1,19 @@ +import { ObjectType } from '../lib'; +import * as ScalarType from './scalar-type-defintions'; + +export const planet = new ObjectType('Planet', { + definition: { + name: ScalarType.string, + diameter: ScalarType.int, + rotationPeriod: ScalarType.int, + orbitalPeriod: ScalarType.int, + gravity: ScalarType.string, + population: ScalarType.list_string, + climates: ScalarType.list_string, + terrains: ScalarType.list_string, + surfaceWater: ScalarType.float, + created: ScalarType.string, + edited: ScalarType.string, + id: ScalarType.required_id, + }, +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts b/packages/@aws-cdk/aws-appsync/test/scalar-type-defintions.ts similarity index 100% rename from packages/@aws-cdk/aws-appsync/test/schema-type-defintions.ts rename to packages/@aws-cdk/aws-appsync/test/scalar-type-defintions.ts From bd6868cdaa05838726aaf602f38cffe8216f6424 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 14:28:10 -0700 Subject: [PATCH 33/78] prevent faiils --- packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts | 2 +- packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 6eb682cf75794..c586a18bad252 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; -import * as t from './schema-type-defintions'; +import * as t from './scalar-type-defintions'; let stack: cdk.Stack; let api: appsync.GraphQLApi; 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 0128ce6085e75..25542722f92e9 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -2,7 +2,7 @@ import { join } from 'path'; import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; -import * as t from './schema-type-defintions'; +import * as t from './scalar-type-defintions'; // Schema Definitions const type = 'type test {\n version: String!\n}\n\n'; From 050b160f51903e2db2ba58d887f6a6412041638a Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 15:01:29 -0700 Subject: [PATCH 34/78] clean up code and finish up readme for object types --- packages/@aws-cdk/aws-appsync/README.md | 66 ++++++++++++++++++- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 15 +++-- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 30 --------- .../aws-appsync/test/integ.graphql-schema.ts | 2 +- 4 files changed, 77 insertions(+), 36 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 30586ab3fbd3d..238f8d2fa1d0b 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -221,4 +221,68 @@ queries, mutations, etc. GraphQL Types can be: More concretely, GraphQL Types are simply the types appended to variables. Referencing the object type Author in the previous example, the GraphQL Types -is `String!` and is applied to both the names `id` and `version`. \ No newline at end of file +is `String!` and is applied to both the names `id` and `version`. + +#### Object Types + +**Object Types** are types that you declare. For example, in the [code-first example](#code-first-example) +the `demo` variable is an **Object Type**. **Object Types** are defined by +GraphQL Types and are only usable when linked to a GraphQL Api. + +You can create Object Types in two ways: + +1. Object Types can be created ***externally*** from a GraphQL API. + ```ts + const api = new appsync.GraphQLApi(stack, 'Api', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); + const demo = new appsync.ObjectType('Demo', { + defintion: { + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + + api.appendToSchema(object); + ``` + This method allows for reusability and modularity, ideal for larger projects. + For example, imagine moving all Object Type definition outside the stack. + + `scalar-types.ts` - a file for scalar type definitions + ```ts + export const required_string = new appsync.GraphqlType.string({ isRequired: true }); + ``` + + `object-types.ts` - a file for object type definitions + ```ts + import { required_string } from './scalar-types'; + export const demo = new appsync.ObjectType('Demo', { + defintion: { + id: required_string, + version: required_string, + }, + }); + ``` + + `cdk-stack.ts` - a file containing our cdk stack + ```ts + import * as gqlType from './object-types'; + const demo = gqlType.demo; + api.appendToSchema(demo); + ``` +2. Object Types can be created ***internally*** within the GraphQL API. + ```ts + const api = new appsync.GraphQLApi(stack, 'Api', { + name: 'demo', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); + api.addType('Demo', { + defintion: { + id: appsync.GraphqlType.string({ isRequired: true }), + version: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + ``` + This method provides easy use and is ideal for smaller projects. + \ 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 e4886a46e2e9b..e0b8a934e00d9 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -715,12 +715,18 @@ export class GraphQLApi extends Construct { * @param delimiter the delimiter between schema and addition * @default - '' */ - public appendToSchema(addition: string, delimiter?: string): void { + public appendToSchema(addition: string | ObjectType, delimiter?: string): void { if ( this.schemaMode != SchemaDefinition.CODE ) { throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); } + let add: string = ''; + if (addition instanceof ObjectType) { + add = addition.toString(); + } else if (typeof(addition) === 'string') { + add = addition; + } const sep = delimiter ?? ''; - this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; + this.schema.definition = `${this.schema.definition}${sep}${add}\n`; } /** @@ -733,10 +739,11 @@ export class GraphQLApi extends Construct { if ( this.schemaMode != SchemaDefinition.CODE ) { throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); }; - return new ObjectType(name, { - api: this, + const type = new ObjectType(name, { definition: props.definition, directives: props.directives, }); + this.appendToSchema(type); + return type; } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 74d8188c5196d..f9d302f7a8717 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -1,5 +1,3 @@ -import { GraphQLApi } from './graphqlapi'; - /** * Directives for types * @@ -35,12 +33,6 @@ export class Directive { * Properties for configuring an Object Type */ export interface ObjectTypeProps { - /** - * the api linked to this object type - * - * @default - no linked GraphQL Api - */ - readonly api?: GraphQLApi; /** * the attributes of this object type */ @@ -71,12 +63,6 @@ export class ObjectType { * the name of this object type */ public readonly name: string; - /** - * the api linked to this object type - * - * @defualt - no linked GraphQL Api - */ - public readonly api?: GraphQLApi; /** * the attributes of this object type */ @@ -90,13 +76,8 @@ export class ObjectType { public constructor(name: string, props: ObjectTypeProps) { this.name = name; - this.api = props.api; this.definition = props.definition; this.directives = props.directives; - - if (this.api) { - this.appendToSchema(this.api); - } } /** @@ -144,17 +125,6 @@ export class ObjectType { }); return schemaAddition; } - - /** - * Add object type definition to a linked api. - * - * @param api - the GraphQL Api to append to - * @param delimiter the delimiter between schema and addition - * @default - '' - */ - public appendToSchema(api: GraphQLApi, delimiter?: string): void{ - api.appendToSchema(this.toString(), delimiter); - } } /** diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index bf8797e4c13b0..dbac9b57e359d 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -25,7 +25,7 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { }); const planet = ObjectType.planet; -planet.appendToSchema(api); +api.appendToSchema(planet); api.addType('Species', { definition: { From 08aa7201e03c09262d2a4a9205fc825c9cce7938 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 10 Aug 2020 15:10:50 -0700 Subject: [PATCH 35/78] style change to readme --- packages/@aws-cdk/aws-appsync/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 238f8d2fa1d0b..7086ef1d1c03f 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -246,7 +246,7 @@ You can create Object Types in two ways: api.appendToSchema(object); ``` - This method allows for reusability and modularity, ideal for larger projects. + > This method allows for reusability and modularity, ideal for larger projects. For example, imagine moving all Object Type definition outside the stack. `scalar-types.ts` - a file for scalar type definitions @@ -284,5 +284,5 @@ You can create Object Types in two ways: }, }); ``` - This method provides easy use and is ideal for smaller projects. + > This method provides easy use and is ideal for smaller projects. \ No newline at end of file From d5bd67cb161d2275bdcb5579b97547d5cb5c5d96 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 11 Aug 2020 15:25:09 -0700 Subject: [PATCH 36/78] interfaces implemented --- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 66 ++++++++++++------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index f9d302f7a8717..b9795eacb3a9c 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -30,13 +30,45 @@ export class Directive { } /** - * Properties for configuring an Object Type + * Properties for configuring an type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } */ -export interface ObjectTypeProps { +export interface BaseTypeProps { /** - * the attributes of this object type + * the attributes of this type */ readonly definition: { [key: string]: GraphqlType }; +} + +/** + * Properties for configuring an Interface Type + */ +export class InterfaceType { + /** + * the name of this type + */ + public readonly name: string; + /** + * the attributes of this type + */ + public readonly definition: { [key: string]: GraphqlType }; + + public constructor(name: string, props: BaseTypeProps) { + this.name = name; + this.definition = props.definition; + } +} + +/** + * Properties for configuring an Object Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * @param directives - the directives for this object type + */ +export interface ObjectTypeProps extends BaseTypeProps { /** * the directives for this object type * @@ -46,27 +78,18 @@ export interface ObjectTypeProps { } /** - * Object Types must be configured using addType to be added to schema + * Object Types are types declared by you. */ -export class ObjectType { +export class ObjectType extends InterfaceType { /** - * A method to define Object Types from an interface - * TODO: implement interface class + * A method to define Object Types from an interface */ - public static extendInterface(name: string, typeInterface: ObjectType, props: ObjectTypeProps): ObjectType { - // check to make sure interface is properly scoped out - typeInterface; - return new ObjectType(name, props); + public static fromInterface(name: string, interfaceType: InterfaceType, props: ObjectTypeProps): ObjectType { + return new ObjectType(name, { + definition: Object.assign({}, props.definition, interfaceType.definition), + directives: props.directives, + }); } - - /** - * the name of this object type - */ - public readonly name: string; - /** - * the attributes of this object type - */ - public readonly definition: { [key: string]: GraphqlType }; /** * the directives for this object type * @@ -75,8 +98,7 @@ export class ObjectType { public readonly directives?: Directive[]; public constructor(name: string, props: ObjectTypeProps) { - this.name = name; - this.definition = props.definition; + super(name, props); this.directives = props.directives; } From 14093f623e26996213a26ac535d7d6411b10062e Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 12 Aug 2020 18:53:32 -0700 Subject: [PATCH 37/78] implemeneted resolvable fields --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 4 +- packages/@aws-cdk/aws-appsync/lib/index.ts | 1 + .../@aws-cdk/aws-appsync/lib/schema-types.ts | 343 +++++++----------- .../@aws-cdk/aws-appsync/lib/schema-utils.ts | 221 +++++++++++ 4 files changed, 364 insertions(+), 205 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-utils.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index e0b8a934e00d9..92db90d334cda 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -6,7 +6,7 @@ import { IFunction } from '@aws-cdk/aws-lambda'; import { Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource } from './data-source'; -import { ObjectType, ObjectTypeProps } from './schema-types'; +import { ObjectType, ObjectTypeProps, InterfaceType } from './schema-types'; /** * enum with all possible values for AppSync authorization type @@ -715,7 +715,7 @@ export class GraphQLApi extends Construct { * @param delimiter the delimiter between schema and addition * @default - '' */ - public appendToSchema(addition: string | ObjectType, delimiter?: string): void { + public appendToSchema(addition: string | ObjectType | InterfaceType, delimiter?: string): void { if ( this.schemaMode != SchemaDefinition.CODE ) { throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); } diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index e0b60e1451a52..fab68945b837a 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -5,4 +5,5 @@ export * from './data-source'; export * from './mapping-template'; export * from './resolver'; export * from './schema-types'; +export * from './schema-utils'; export * from './graphqlapi'; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index b9795eacb3a9c..b1073a0f584d1 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -1,153 +1,6 @@ -/** - * Directives for types - * - * i.e. @aws_iam or @aws_subscribe - */ -export class Directive { - /** - * Add the @aws_iam directive - */ - public static iam(): Directive{ - return new Directive('@aws_iam'); - } - - /** - * Add a custom directive - * - * @param statement - the directive statement to append - * Note: doesn't guarantee functionality - */ - public static custom(statement: string): Directive { - return new Directive(statement); - } - - /** - * the directive statement - */ - public readonly statement: string; - - private constructor(statement: string) { this.statement = statement; } -} - -/** - * Properties for configuring an type - * - * @param definition - the variables and types that define this type - * i.e. { string: GraphqlType, string: GraphqlType } - */ -export interface BaseTypeProps { - /** - * the attributes of this type - */ - readonly definition: { [key: string]: GraphqlType }; -} - -/** - * Properties for configuring an Interface Type - */ -export class InterfaceType { - /** - * the name of this type - */ - public readonly name: string; - /** - * the attributes of this type - */ - public readonly definition: { [key: string]: GraphqlType }; - - public constructor(name: string, props: BaseTypeProps) { - this.name = name; - this.definition = props.definition; - } -} - -/** - * Properties for configuring an Object Type - * - * @param definition - the variables and types that define this type - * i.e. { string: GraphqlType, string: GraphqlType } - * @param directives - the directives for this object type - */ -export interface ObjectTypeProps extends BaseTypeProps { - /** - * the directives for this object type - * - * @default - no directives - */ - readonly directives?: Directive []; -} - -/** - * Object Types are types declared by you. - */ -export class ObjectType extends InterfaceType { - /** - * A method to define Object Types from an interface - */ - public static fromInterface(name: string, interfaceType: InterfaceType, props: ObjectTypeProps): ObjectType { - return new ObjectType(name, { - definition: Object.assign({}, props.definition, interfaceType.definition), - directives: props.directives, - }); - } - /** - * the directives for this object type - * - * @default - no directives - */ - public readonly directives?: Directive[]; - - public constructor(name: string, props: ObjectTypeProps) { - super(name, props); - this.directives = props.directives; - } - - /** - * Create an GraphQL Type representing this Object Type - * - * @param options the options to configure this attribute - * - isList - * - isRequired - * - isRequiredList - */ - public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ - return GraphqlType.graphqlObject({ - isList: options?.isList, - isRequired: options?.isRequired, - isRequiredList: options?.isRequiredList, - objectType: this, - }); - } - - /** - * Generate the string of this object type - */ - public toString(): string { - const directives = this.generateDirectives(this.directives); - let schemaAddition = `type ${this.name} ${directives}{\n`; - Object.keys(this.definition).forEach( (key) => { - const attribute = this.definition[key]; - schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; - }); - return `${schemaAddition}}`; - } - - /** - * Utility function to generate directives - * - * @param directives the directives of a given type - * @param delimiter the separator betweeen directives - * @default - ' ' - */ - private generateDirectives(directives?: Directive[], delimiter?: string): string{ - let schemaAddition = ''; - if (!directives){ return schemaAddition; } - directives.map((directive) => { - schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; - }); - return schemaAddition; - } -} +import { Resolver } from './resolver'; +import { Directive, Type, ResolvableField, ResolvableFieldOptions } from './schema-utils'; +import { BaseDataSource } from './data-source'; /** * Base options for GraphQL Types @@ -457,89 +310,173 @@ export class GraphqlType { } /** - * Enum containing the Types that can be used to define ObjectTypes + * Properties for configuring an type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } */ -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', +export interface BaseTypeProps { /** - * `Int` scalar type is a signed non-fractional numerical value. + * the attributes of this type */ - INT = 'Int', + readonly definition: { [key: string]: GraphqlType | ResolvableField }; +} + +/** + * Properties for configuring an Interface Type + */ +export class InterfaceType { /** - * `Float` scalar type is a signed double-precision fractional value. + * the name of this type */ - FLOAT = 'Float', + public readonly name: string; /** - * `Boolean` scalar type is a boolean value: true or false. + * the attributes of this type */ - BOOLEAN = 'Boolean', + public readonly definition: { [key: string]: GraphqlType | ResolvableField }; + + public constructor(name: string, props: BaseTypeProps) { + this.name = name; + this.definition = props.definition; + } /** - * `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 + * Generate the string of this object type */ - AWS_DATE = 'AWSDate', + public toString(): string { + let schemaAddition = `interface ${this.name}{\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } +} + +/** + * Properties for configuring an Object Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * @param directives - the directives for this object type + */ +export interface ObjectTypeProps extends BaseTypeProps { /** - * `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. + * the directives for this object type * - * @see https://en.wikipedia.org/wiki/ISO_8601#Times + * @default - no directives */ - AWS_TIME = 'AWSTime', + readonly directives?: Directive[]; +} + +/** + * Object Types are types declared by you. + */ +export class ObjectType extends InterfaceType { /** - * `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 + * A method to define Object Types from an interface */ - AWS_DATE_TIME = 'AWSDateTime', + public static fromInterface(name: string, interfaceType: InterfaceType, props: ObjectTypeProps): ObjectType { + return new ObjectType(name, { + definition: Object.assign({}, props.definition, interfaceType.definition), + directives: props.directives, + }); + } /** - * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * the directives for this object type * - * Timestamps are serialized and deserialized as numbers. + * @default - no directives */ - AWS_TIMESTAMP = 'AWSTimestamp', + public readonly directives?: Directive[]; /** - * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + * The resolvers linked to this data source */ - AWS_EMAIL = 'AWSEmail', + public resolvers?: Resolver[]; + + public constructor(name: string, props: ObjectTypeProps) { + super(name, props); + this.directives = props.directives; + + Object.keys(this.definition).forEach((fieldName) => { + const fieldInfo = this.definition[fieldName]; + if(fieldInfo instanceof ResolvableField) { + this.resolvers?.push(this.generateResolver(fieldName, fieldInfo)); + } + }); + } + /** - * `AWSJson` scalar type represents a JSON string. + * Create an GraphQL Type representing this Object Type + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList */ - AWS_JSON = 'AWSJSON', + public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ + return GraphqlType.graphqlObject({ + isList: options?.isList, + isRequired: options?.isRequired, + isRequiredList: options?.isRequiredList, + objectType: this, + }); + } + /** - * `AWSURL` scalar type represetns a valid URL string. + * Add a resolvable field to this Object Type * - * URLs wihtout schemes or contain double slashes are considered invalid. + * @param fieldName - + * @param type - + * @param dataSource - + * @param options - */ - AWS_URL = 'AWSURL', + public addResolvableField(fieldName: string, type: GraphqlType, dataSource: BaseDataSource, options?: ResolvableFieldOptions): Resolver{ + const resolvableField = new ResolvableField(type, dataSource, options); + const resolver = this.generateResolver(fieldName, resolvableField); + this.resolvers?.push(resolver); + this.definition[fieldName] = resolvableField; + return resolver; + } + /** - * `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. + * Generate the string of this object type */ - AWS_PHONE = 'AWSPhone', + public toString(): string { + const directives = this.generateDirectives(this.directives); + let schemaAddition = `type ${this.name} ${directives}{\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + const args = attribute instanceof ResolvableField ? attribute.argsToString() : ''; + schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } + /** - * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + * Utility function to generate directives + * + * @param directives the directives of a given type + * @param delimiter the separator betweeen directives + * @default - ' ' */ - AWS_IP_ADDRESS = 'AWSIPAddress', + private generateDirectives(directives?: Directive[], delimiter?: string): string{ + let schemaAddition = ''; + if (!directives){ return schemaAddition; } + directives.map((directive) => { + schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; + }); + return schemaAddition; + } /** - * Type used for Object Types + * Generate the resolvers linked to this Object Type */ - OBJECT = 'OBJECT', + protected generateResolver(fieldName: string, resolvableField: ResolvableField): Resolver{ + return resolvableField.dataSource.createResolver({ + typeName: this.name, + fieldName: fieldName, + requestMappingTemplate: resolvableField.requestMappingTemplate, + responseMappingTemplate: resolvableField.responseMappingTemplate, + }); + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts b/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts new file mode 100644 index 0000000000000..d49ad2f983c77 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts @@ -0,0 +1,221 @@ +import { BaseDataSource } from './data-source'; +import { MappingTemplate } from './mapping-template'; +import { GraphqlType } from './schema-types'; + +/** + * Directives for types + * + * i.e. @aws_iam or @aws_subscribe + */ +export class Directive { + /** + * Add the @aws_iam directive + */ + public static iam(): Directive{ + return new Directive('@aws_iam'); + } + + /** + * Add a custom directive + * + * @param statement - the directive statement to append + * Note: doesn't guarantee functionality + */ + public static custom(statement: string): Directive { + return new Directive(statement); + } + + /** + * the directive statement + */ + public readonly statement: string; + + private constructor(statement: string) { this.statement = statement; } +} + +/** + * Properties for configuring a resolvable field + * + * @options args - the variables and types that define the arguments + * + * i.e. { string: GraphqlType, string: GraphqlType } + * + * @options requestMappingTemplate - the mapping template for requests to this resolver + * @options responseMappingTemplate - the mapping template for responses from this resolver + */ +export interface ResolvableFieldOptions { + /** + * The arguments for this resolvable field. + * + * i.e. type Example (first: String second: String) {} + * - where 'first' and 'second' are key values for args + * and 'String' is the GraphqlType + * + * @default - no arguments + */ + readonly args?: { [key: string]: GraphqlType }; + /** + * The request mapping template for this resolver + * + * @default - No mapping template + */ + readonly requestMappingTemplate?: MappingTemplate; + /** + * The response mapping template for this resolver + * + * @default - No mapping template + */ + readonly responseMappingTemplate?: MappingTemplate; +} + +/** + * Resolvable Fields build upon Graphql Types and provide fields + * that can resolve into operations on a data source. + */ +export class ResolvableField { + /** + * The return type for this field + */ + readonly graphqlType: GraphqlType; + /** + * The data source creating linked to this resolvable field + */ + readonly dataSource: BaseDataSource; + /** + * The arguments for this resolvable field. + * + * i.e. type Example (first: String second: String) {} + * - where 'first' and 'second' are key values for args + * and 'String' is the GraphqlType + * + * @default - no arguments + */ + readonly args?: { [key: string]: GraphqlType }; + /** + * The request mapping template for this resolver + * + * @default - No mapping template + */ + readonly requestMappingTemplate?: MappingTemplate; + /** + * The response mapping template for this resolver + * + * @default - No mapping template + */ + readonly responseMappingTemplate?: MappingTemplate; + + public constructor(type: GraphqlType, dataSource: BaseDataSource, options?: ResolvableFieldOptions) { + this.graphqlType = type; + this.dataSource = dataSource; + this.args = options?.args; + this.requestMappingTemplate = options?.requestMappingTemplate; + this.responseMappingTemplate = options?.responseMappingTemplate; + } + + /** + * Generate the string of this resolvable field + */ + public toString(): string{ + return this.graphqlType.toString(); + } + + /** + * Generate the args string of this resolvable field + */ + public argsToString(): string{ + let args = '( '; + Object.keys(this.args ?? {}).forEach((key) => { + const type = this.args?.[key].toString(); + args = `${args}${key}: ${type} `; + }); + return `${args})`; + } +} + +/** + * 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 + */ + AWS_DATE = '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 + */ + AWS_TIME = '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 + */ + AWS_DATE_TIME = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWS_TIMESTAMP = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWS_EMAIL = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWS_JSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWS_URL = '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. + */ + AWS_PHONE = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWS_IP_ADDRESS = 'AWSIPAddress', + + /** + * Type used for Object Types + */ + OBJECT = 'OBJECT', +} \ No newline at end of file From 315ffbf4555dc321560a70d92afd10e0adfbe147 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 13 Aug 2020 12:04:51 -0700 Subject: [PATCH 38/78] clean up some implementation and some docs --- packages/@aws-cdk/aws-appsync/README.md | 79 ++++++++++-- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 16 ++- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 109 +++++++++++------ .../test/appsync-code-first.test.ts | 114 +++++++++++++++++- .../aws-appsync/test/integ.graphql-schema.ts | 2 +- 5 files changed, 264 insertions(+), 56 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 7086ef1d1c03f..02e38fb22e5c8 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -170,6 +170,8 @@ A code-first approach offers a developer workflow with: - **reusability**: simplifying down boilerplate/repetitive code - **consistency**: resolvers and schema definition will always be synced +The code-first approach allows for dynamic schema generation. You can generate your schema based on variables and templates to reduce code duplication. + #### Code-First Example We are going to reference the [example](#Example) through a code-first approach. @@ -214,24 +216,66 @@ const demo = api.addType('demo', { One of the benefits of GraphQL is its strongly typed nature. We define the types within an object, query, mutation, interface, etc. as **GraphQL Types**. -GraphQL Types are the building blocks of types, whether they are object, -queries, mutations, etc. GraphQL Types can be: +GraphQL Types are the building blocks of types, whether they are scalar, objects, +interfaces, etc. GraphQL Types can be: - [**Scalar Types**](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html): Id, Int, String, AWSDate, etc. - **Object Types**: types that you generate (i.e. `demo` from the example above) +- **Interface Types**: abstract types that define the base implementation of other +Intermediate Types More concretely, GraphQL Types are simply the types appended to variables. -Referencing the object type Author in the previous example, the GraphQL Types +Referencing the object type `Demo` in the previous example, the GraphQL Types is `String!` and is applied to both the names `id` and `version`. +#### Intermediate Types + +Intermediate Types are abstractions above Scalar Types. They have a set of defined +fields, where each field corresponds to another type in the system. Intermediate +Types will be the meat of your GraphQL Schema as they are the types defined by you. + +Intermediate Types include: +- [**Interface Types**](#Interface-Types) +- [**Object Types**](#Object-Types) + +#### Interface Types + +**Interface Types** are abstract types that define the implementation of other +intermediate types. They are useful for eliminating duplication and can be used +to generate Object Types with less work. + +You can create Interface Types in two ways: + +1. Interface Types can be created ***externally***. + ```ts + const node = new appsync.InterfaceType('Node', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + ``` +2. Interface Types can be created ***externally*** from another Interface Type. + ```ts + const node = new appsync.InterfaceType('Node', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + const superNode = new appsync.InterfaceType.extendInterface('SuperNode', node, { + definition: { + speicalId: appsync.GraphqlType.string(), + }, + }); + ``` + #### Object Types **Object Types** are types that you declare. For example, in the [code-first example](#code-first-example) the `demo` variable is an **Object Type**. **Object Types** are defined by GraphQL Types and are only usable when linked to a GraphQL Api. -You can create Object Types in two ways: +You can create Object Types in three ways: -1. Object Types can be created ***externally*** from a GraphQL API. +1. Object Types can be created ***externally***. ```ts const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', @@ -244,7 +288,7 @@ You can create Object Types in two ways: }, }); - api.appendToSchema(object); + api.appendToSchema(object.toString()); ``` > This method allows for reusability and modularity, ideal for larger projects. For example, imagine moving all Object Type definition outside the stack. @@ -267,11 +311,26 @@ You can create Object Types in two ways: `cdk-stack.ts` - a file containing our cdk stack ```ts - import * as gqlType from './object-types'; - const demo = gqlType.demo; - api.appendToSchema(demo); + import { demo } from './object-types'; + api.appendToSchema(demo.toString()); ``` -2. Object Types can be created ***internally*** within the GraphQL API. + +2. Object Types can be created ***externally*** from an Interface Type. + ```ts + const node = new appsync.InterfaceType('Node', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + const demo = new appsync.ObjectType.implementInterface('Demo', node, { + defintion: { + version: appsync.GraphqlType.string({ isRequired: true }), + }, + }); + ``` + > This method allows for reusability and modularity, ideal for reducing code duplication. + +3. Object Types can be created ***internally*** within the GraphQL API. ```ts const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index e0b8a934e00d9..7f8e0ca6e59dc 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -714,19 +714,15 @@ export class GraphQLApi extends Construct { * @param addition the addition to add to schema * @param delimiter the delimiter between schema and addition * @default - '' + * + * @experimental */ - public appendToSchema(addition: string | ObjectType, delimiter?: string): void { + public appendToSchema(addition: string, delimiter?: string): void { if ( this.schemaMode != SchemaDefinition.CODE ) { throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); } - let add: string = ''; - if (addition instanceof ObjectType) { - add = addition.toString(); - } else if (typeof(addition) === 'string') { - add = addition; - } const sep = delimiter ?? ''; - this.schema.definition = `${this.schema.definition}${sep}${add}\n`; + this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; } /** @@ -734,6 +730,8 @@ export class GraphQLApi extends Construct { * * @param name the name of the object type * @param props the definition + * + * @experimental */ public addType(name: string, props: ObjectTypeProps): ObjectType { if ( this.schemaMode != SchemaDefinition.CODE ) { @@ -743,7 +741,7 @@ export class GraphQLApi extends Construct { definition: props.definition, directives: props.directives, }); - this.appendToSchema(type); + this.appendToSchema(type.toString()); return type; } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index b9795eacb3a9c..cce3d776eaed5 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -2,6 +2,8 @@ * Directives for types * * i.e. @aws_iam or @aws_subscribe + * + * @experimental */ export class Directive { /** @@ -34,6 +36,8 @@ export class Directive { * * @param definition - the variables and types that define this type * i.e. { string: GraphqlType, string: GraphqlType } + * + * @experimental */ export interface BaseTypeProps { /** @@ -43,9 +47,20 @@ export interface BaseTypeProps { } /** - * Properties for configuring an Interface Type + * Interface Types are abstract types that includes a certain set of fields + * that other types must include if they implement the interface. + * + * @experimental */ export class InterfaceType { + /** + * A method to extend an Interface Type from another interface + */ + public static extendInterface(name: string, interfaceType: InterfaceType, props: BaseTypeProps): InterfaceType { + return new InterfaceType(name, { + definition: Object.assign({}, props.definition, interfaceType.definition), + }); + } /** * the name of this type */ @@ -59,6 +74,35 @@ export class InterfaceType { this.name = name; this.definition = props.definition; } + + /** + * Create an GraphQL Type representing this Intermediate Type + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ + return GraphqlType.intermediate({ + isList: options?.isList, + isRequired: options?.isRequired, + isRequiredList: options?.isRequiredList, + intermediateType: this, + }); + } + + /** + * Generate the string of this object type + */ + public toString(): string { + let schemaAddition = `interface ${this.name} {\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } } /** @@ -67,6 +111,8 @@ export class InterfaceType { * @param definition - the variables and types that define this type * i.e. { string: GraphqlType, string: GraphqlType } * @param directives - the directives for this object type + * + * @experimental */ export interface ObjectTypeProps extends BaseTypeProps { /** @@ -79,12 +125,14 @@ export interface ObjectTypeProps extends BaseTypeProps { /** * Object Types are types declared by you. + * + * @experimental */ export class ObjectType extends InterfaceType { /** * A method to define Object Types from an interface */ - public static fromInterface(name: string, interfaceType: InterfaceType, props: ObjectTypeProps): ObjectType { + public static implementInterface(name: string, interfaceType: InterfaceType, props: ObjectTypeProps): ObjectType { return new ObjectType(name, { definition: Object.assign({}, props.definition, interfaceType.definition), directives: props.directives, @@ -102,23 +150,6 @@ export class ObjectType extends InterfaceType { this.directives = props.directives; } - /** - * Create an GraphQL Type representing this Object Type - * - * @param options the options to configure this attribute - * - isList - * - isRequired - * - isRequiredList - */ - public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ - return GraphqlType.graphqlObject({ - isList: options?.isList, - isRequired: options?.isRequired, - isRequiredList: options?.isRequiredList, - objectType: this, - }); - } - /** * Generate the string of this object type */ @@ -155,6 +186,8 @@ export class ObjectType extends InterfaceType { * @option isList - is this attribute a list * @option isRequired - is this attribute non-nullable * @option isRequiredList - is this attribute a non-nullable list + * + * @experimental */ export interface BaseGraphqlTypeOptions { /** @@ -190,13 +223,15 @@ export interface BaseGraphqlTypeOptions { * @option isRequired - is this attribute non-nullable * @option isRequiredList - is this attribute a non-nullable list * @option objectType - the object type linked to this attribute + * + * @experimental */ export interface GraphqlTypeOptions extends BaseGraphqlTypeOptions { /** - * the object type linked to this attribute - * @default - no object type + * the intermediate type linked to this attribute + * @default - no intermediate type */ - readonly objectType?: ObjectType; + readonly intermediateType?: InterfaceType; } /** @@ -380,19 +415,20 @@ export class GraphqlType { } /** - * an object type to be added as an attribute + * an intermediate type to be added as an attribute + * (i.e. an interface or an object type) * * @param options the options to configure this attribute * - isList * - isRequired * - isRequiredList - * - objectType + * - intermediateType */ - public static graphqlObject(options?: GraphqlTypeOptions): GraphqlType { - if (!options?.objectType) { - throw new Error('GraphQL Type of object must be configured with corresponding Object Type'); + public static intermediate(options?: GraphqlTypeOptions): GraphqlType { + if (!options?.intermediateType) { + throw new Error('GraphQL Type of interface must be configured with corresponding Intermediate Type'); } - return new GraphqlType(Type.OBJECT, options); + return new GraphqlType(Type.INTERMEDIATE, options); } /** @@ -427,17 +463,19 @@ export class GraphqlType { public readonly isRequiredList: boolean; /** - * the object type linked to this attribute - * @default - no object type + * the intermediate type linked to this attribute + * (i.e. an interface or an object) + * + * @default - no intermediate type */ - public readonly objectType?: ObjectType; + public readonly intermediateType?: InterfaceType; private constructor(type: Type, options?: GraphqlTypeOptions) { this.type = type; this.isList = options?.isList ?? false; this.isRequired = options?.isRequired ?? false; this.isRequiredList = options?.isRequiredList ?? false; - this.objectType = options?.objectType; + this.intermediateType = options?.intermediateType; } /** @@ -445,7 +483,7 @@ export class GraphqlType { */ public toString(): string{ // If an Object Type, we use the name of the Object Type - let type = this.objectType ? this.objectType?.name : this.type; + let type = this.intermediateType ? this.intermediateType?.name : this.type; // If configured as required, the GraphQL Type becomes required type = this.isRequired ? `${type}!` : type; // If configured with isXxxList, the GraphQL Type becomes a list @@ -539,7 +577,8 @@ export enum Type { AWS_IP_ADDRESS = 'AWSIPAddress', /** - * Type used for Object Types + * Type used for Intermediate Types + * (i.e. an interface or an object type) */ - OBJECT = 'OBJECT', + INTERMEDIATE = 'INTERMEDIATE', } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index c586a18bad252..8ddd4bff54019 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -248,4 +248,116 @@ describe('testing all GraphQL Types', () => { Definition: `${out}`, }); }); -}); \ No newline at end of file +}); + +describe('testing InterfaceType properties', () => { + let baseTest: appsync.InterfaceType; + beforeEach(()=>{ + baseTest = new appsync.InterfaceType('baseTest', { + definition: { + id: t.id, + }, + }); + }); + test('basic InterfaceType produces correct schema', () => { + // WHEN + api.appendToSchema(baseTest.toString()); + const out = 'interface baseTest {\n id: ID\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('nested InterfaceTypes produce correct schema', () => { + // WHEN + const test = appsync.InterfaceType.extendInterface('Test', baseTest, { + definition: { + id2: t.id, + }, + }); + api.appendToSchema(baseTest.toString()); + api.appendToSchema(test.toString()); + const out = 'interface baseTest {\n id: ID\n}\ninterface Test {\n id2: ID\n id: ID\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Interface Type can be a Graphql Type', () => { + // WHEN + const graphqlType = baseTest.attribute(); + + const test = new appsync.ObjectType('Test', { + definition: { + test: graphqlType, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: baseTest\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); + +describe('testing Object Type properties', () => { + + test('ObjectType can extend from interface types', () => { + // WHEN + const baseTest = new appsync.InterfaceType('baseTest', { + definition: { + id: t.id, + }, + }); + const test = appsync.InterfaceType.extendInterface('Test', baseTest, { + definition: { + id2: t.id, + }, + }); + const objectTest = appsync.ObjectType.implementInterface('objectTest', test, { + definition: { + id3: t.id, + }, + directives: [appsync.Directive.custom('@test')], + }); + + api.appendToSchema(test.toString()); + api.appendToSchema(objectTest.toString()); + const gql_interface = 'interface Test {\n id2: ID\n id: ID\n}\n'; + const gql_object = 'type objectTest @test {\n id3: ID\n id2: ID\n id: ID\n}\n'; + const out = `${gql_interface}${gql_object}`; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can be a Graphql Type', () => { + // WHEN + const baseTest = new appsync.ObjectType('baseTest', { + definition: { + id: t.id, + }, + }); + const graphqlType = baseTest.attribute(); + const test = new appsync.ObjectType('Test', { + definition: { + test: graphqlType, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: baseTest\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index dbac9b57e359d..6af69056c268a 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -25,7 +25,7 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { }); const planet = ObjectType.planet; -api.appendToSchema(planet); +api.appendToSchema(planet.toString()); api.addType('Species', { definition: { From de4c2add382fdad6ea92a35f98f65344385fdc4b Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 13 Aug 2020 16:25:32 -0700 Subject: [PATCH 39/78] edit schema --- packages/@aws-cdk/aws-appsync/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 02e38fb22e5c8..63594a282d6bf 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -182,8 +182,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'), + schemaDefinition: appsync.SchemaDefinition.CODE, authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM From 2dc962bcf733d6c9425ab49545110af215881106 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 13 Aug 2020 16:56:32 -0700 Subject: [PATCH 40/78] make sure implement interface is appended --- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 44 ++++++++++++++----- .../test/appsync-code-first.test.ts | 15 ++++--- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index cce3d776eaed5..d1d83b839221d 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -32,14 +32,21 @@ export class Directive { } /** - * Properties for configuring an type + * Properties for configuring an Intermediate Type * + * @param interface - the interface that this intermediate type implements * @param definition - the variables and types that define this type * i.e. { string: GraphqlType, string: GraphqlType } * * @experimental */ -export interface BaseTypeProps { +export interface IntermediateTypeProps { + /** + * The Interface Type this Object Type implements + * + * @default - no interface type + */ + readonly interfaceType?: InterfaceType; /** * the attributes of this type */ @@ -56,23 +63,34 @@ export class InterfaceType { /** * A method to extend an Interface Type from another interface */ - public static extendInterface(name: string, interfaceType: InterfaceType, props: BaseTypeProps): InterfaceType { + public static extendInterface(name: string, props: IntermediateTypeProps): InterfaceType { + if (!props.interfaceType) { + throw new Error('Static function `extendInterface` requires an interfaceType to implement'); + } return new InterfaceType(name, { - definition: Object.assign({}, props.definition, interfaceType.definition), + interfaceType: props.interfaceType, + definition: Object.assign({}, props.definition, props?.interfaceType.definition), }); } /** * the name of this type */ public readonly name: string; + /** + * The Interface Type this Intermediate Type implements + * + * @default - no interface type + */ + public readonly interfaceType?: InterfaceType; /** * the attributes of this type */ public readonly definition: { [key: string]: GraphqlType }; - public constructor(name: string, props: BaseTypeProps) { + public constructor(name: string, props: IntermediateTypeProps) { this.name = name; this.definition = props.definition; + this.interfaceType = props.interfaceType; } /** @@ -96,7 +114,8 @@ export class InterfaceType { * Generate the string of this object type */ public toString(): string { - let schemaAddition = `interface ${this.name} {\n`; + const title = this.interfaceType ? `${this.name} implements ${this.interfaceType.name}` : this.name; + let schemaAddition = `interface ${title} {\n`; Object.keys(this.definition).forEach( (key) => { const attribute = this.definition[key]; schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; @@ -114,7 +133,7 @@ export class InterfaceType { * * @experimental */ -export interface ObjectTypeProps extends BaseTypeProps { +export interface ObjectTypeProps extends IntermediateTypeProps { /** * the directives for this object type * @@ -132,9 +151,13 @@ export class ObjectType extends InterfaceType { /** * A method to define Object Types from an interface */ - public static implementInterface(name: string, interfaceType: InterfaceType, props: ObjectTypeProps): ObjectType { + public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { + if (!props.interfaceType) { + throw new Error('Static function `implementInterface` requires an interfaceType to implement'); + } return new ObjectType(name, { - definition: Object.assign({}, props.definition, interfaceType.definition), + interfaceType: props.interfaceType, + definition: Object.assign({}, props.definition, props.interfaceType?.definition), directives: props.directives, }); } @@ -154,8 +177,9 @@ export class ObjectType extends InterfaceType { * Generate the string of this object type */ public toString(): string { + const title = this.interfaceType ? `${this.name} implements ${this.interfaceType.name}` : this.name; const directives = this.generateDirectives(this.directives); - let schemaAddition = `type ${this.name} ${directives}{\n`; + let schemaAddition = `type ${title} ${directives}{\n`; Object.keys(this.definition).forEach( (key) => { const attribute = this.definition[key]; schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 8ddd4bff54019..859ee2bd27472 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -272,14 +272,15 @@ describe('testing InterfaceType properties', () => { test('nested InterfaceTypes produce correct schema', () => { // WHEN - const test = appsync.InterfaceType.extendInterface('Test', baseTest, { + const test = appsync.InterfaceType.extendInterface('Test', { + interfaceType: baseTest, definition: { id2: t.id, }, }); api.appendToSchema(baseTest.toString()); api.appendToSchema(test.toString()); - const out = 'interface baseTest {\n id: ID\n}\ninterface Test {\n id2: ID\n id: ID\n}\n'; + const out = 'interface baseTest {\n id: ID\n}\ninterface Test implements baseTest {\n id2: ID\n id: ID\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { @@ -315,12 +316,14 @@ describe('testing Object Type properties', () => { id: t.id, }, }); - const test = appsync.InterfaceType.extendInterface('Test', baseTest, { + const test = appsync.InterfaceType.extendInterface('Test', { + interfaceType: baseTest, definition: { id2: t.id, }, }); - const objectTest = appsync.ObjectType.implementInterface('objectTest', test, { + const objectTest = appsync.ObjectType.implementInterface('objectTest', { + interfaceType: test, definition: { id3: t.id, }, @@ -329,8 +332,8 @@ describe('testing Object Type properties', () => { api.appendToSchema(test.toString()); api.appendToSchema(objectTest.toString()); - const gql_interface = 'interface Test {\n id2: ID\n id: ID\n}\n'; - const gql_object = 'type objectTest @test {\n id3: ID\n id2: ID\n id: ID\n}\n'; + const gql_interface = 'interface Test implements baseTest {\n id2: ID\n id: ID\n}\n'; + const gql_object = 'type objectTest implements Test @test {\n id3: ID\n id2: ID\n id: ID\n}\n'; const out = `${gql_interface}${gql_object}`; // THEN From 912a6740e38201f84f78762996c7a91a719ac32b Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 14 Aug 2020 12:54:38 -0700 Subject: [PATCH 41/78] bug fix --- packages/@aws-cdk/aws-appsync/README.md | 34 +++----- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 62 +++++++------ .../test/appsync-code-first.test.ts | 86 +++++++++++++------ 3 files changed, 99 insertions(+), 83 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 63594a282d6bf..432f92bdbacb7 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -242,29 +242,14 @@ Intermediate Types include: intermediate types. They are useful for eliminating duplication and can be used to generate Object Types with less work. -You can create Interface Types in two ways: - -1. Interface Types can be created ***externally***. - ```ts - const node = new appsync.InterfaceType('Node', { - definition: { - id: appsync.GraphqlType.string({ isRequired: true }), - }, - }); - ``` -2. Interface Types can be created ***externally*** from another Interface Type. - ```ts - const node = new appsync.InterfaceType('Node', { - definition: { - id: appsync.GraphqlType.string({ isRequired: true }), - }, - }); - const superNode = new appsync.InterfaceType.extendInterface('SuperNode', node, { - definition: { - speicalId: appsync.GraphqlType.string(), - }, - }); - ``` +You can create Interface Types ***externally***. +```ts +const node = new appsync.InterfaceType('Node', { + definition: { + id: appsync.GraphqlType.string({ isRequired: true }), + }, +}); +``` #### Object Types @@ -321,7 +306,8 @@ You can create Object Types in three ways: id: appsync.GraphqlType.string({ isRequired: true }), }, }); - const demo = new appsync.ObjectType.implementInterface('Demo', node, { + const demo = new appsync.ObjectType.implementInterface('Demo', { + interfaceTypes: [ node ], defintion: { version: appsync.GraphqlType.string({ isRequired: true }), }, diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index d1d83b839221d..2649fb41bc64b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -34,19 +34,12 @@ export class Directive { /** * Properties for configuring an Intermediate Type * - * @param interface - the interface that this intermediate type implements * @param definition - the variables and types that define this type * i.e. { string: GraphqlType, string: GraphqlType } * * @experimental */ export interface IntermediateTypeProps { - /** - * The Interface Type this Object Type implements - * - * @default - no interface type - */ - readonly interfaceType?: InterfaceType; /** * the attributes of this type */ @@ -60,28 +53,10 @@ export interface IntermediateTypeProps { * @experimental */ export class InterfaceType { - /** - * A method to extend an Interface Type from another interface - */ - public static extendInterface(name: string, props: IntermediateTypeProps): InterfaceType { - if (!props.interfaceType) { - throw new Error('Static function `extendInterface` requires an interfaceType to implement'); - } - return new InterfaceType(name, { - interfaceType: props.interfaceType, - definition: Object.assign({}, props.definition, props?.interfaceType.definition), - }); - } /** * the name of this type */ public readonly name: string; - /** - * The Interface Type this Intermediate Type implements - * - * @default - no interface type - */ - public readonly interfaceType?: InterfaceType; /** * the attributes of this type */ @@ -90,7 +65,6 @@ export class InterfaceType { public constructor(name: string, props: IntermediateTypeProps) { this.name = name; this.definition = props.definition; - this.interfaceType = props.interfaceType; } /** @@ -114,8 +88,7 @@ export class InterfaceType { * Generate the string of this object type */ public toString(): string { - const title = this.interfaceType ? `${this.name} implements ${this.interfaceType.name}` : this.name; - let schemaAddition = `interface ${title} {\n`; + let schemaAddition = `interface ${this.name} {\n`; Object.keys(this.definition).forEach( (key) => { const attribute = this.definition[key]; schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; @@ -129,17 +102,24 @@ export class InterfaceType { * * @param definition - the variables and types that define this type * i.e. { string: GraphqlType, string: GraphqlType } + * @param interfaceTypes - the interfaces that this object type implements * @param directives - the directives for this object type * * @experimental */ export interface ObjectTypeProps extends IntermediateTypeProps { + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + readonly interfaceTypes?: InterfaceType[]; /** * the directives for this object type * * @default - no directives */ - readonly directives?: Directive []; + readonly directives?: Directive[]; } /** @@ -152,15 +132,23 @@ export class ObjectType extends InterfaceType { * A method to define Object Types from an interface */ public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { - if (!props.interfaceType) { + if (!props.interfaceTypes || !props.interfaceTypes.length) { throw new Error('Static function `implementInterface` requires an interfaceType to implement'); } return new ObjectType(name, { - interfaceType: props.interfaceType, - definition: Object.assign({}, props.definition, props.interfaceType?.definition), + interfaceTypes: props.interfaceTypes, + definition: props.interfaceTypes.reduce((def, interfaceType) => { + return Object.assign({}, def, interfaceType.definition); + }, props.definition), directives: props.directives, }); } + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + public readonly interfaceTypes?: InterfaceType[]; /** * the directives for this object type * @@ -170,6 +158,7 @@ export class ObjectType extends InterfaceType { public constructor(name: string, props: ObjectTypeProps) { super(name, props); + this.interfaceTypes = props.interfaceTypes; this.directives = props.directives; } @@ -177,7 +166,14 @@ export class ObjectType extends InterfaceType { * Generate the string of this object type */ public toString(): string { - const title = this.interfaceType ? `${this.name} implements ${this.interfaceType.name}` : this.name; + let title = this.name; + if(this.interfaceTypes && this.interfaceTypes.length){ + title = `${title} implements`; + this.interfaceTypes.map((interfaceType) => { + title = `${title} ${interfaceType.name},`; + }); + title = title.slice(0, -1); + } const directives = this.generateDirectives(this.directives); let schemaAddition = `type ${title} ${directives}{\n`; Object.keys(this.definition).forEach( (key) => { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 859ee2bd27472..34e0eeaade11c 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -270,24 +270,6 @@ describe('testing InterfaceType properties', () => { }); }); - test('nested InterfaceTypes produce correct schema', () => { - // WHEN - const test = appsync.InterfaceType.extendInterface('Test', { - interfaceType: baseTest, - definition: { - id2: t.id, - }, - }); - api.appendToSchema(baseTest.toString()); - api.appendToSchema(test.toString()); - const out = 'interface baseTest {\n id: ID\n}\ninterface Test implements baseTest {\n id2: ID\n id: ID\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - test('Interface Type can be a Graphql Type', () => { // WHEN const graphqlType = baseTest.attribute(); @@ -309,31 +291,83 @@ describe('testing InterfaceType properties', () => { describe('testing Object Type properties', () => { - test('ObjectType can extend from interface types', () => { + test('errors when no InterfaceTypes are specified', () => { + // WHEN + const when = () => { + appsync.ObjectType.implementInterface('objectTest', { + definition: { + id2: t.id, + }, + }); + }; + + // THEN + expect(when).toThrowError('Static function `implementInterface` requires an interfaceType to implement'); + }); + + test('errors when implementing empty InterfaceTypes properties', () => { + // WHEN + const when = () => { + appsync.ObjectType.implementInterface('objectTest', { + interfaceTypes: [], + definition: { + id2: t.id, + }, + }); + }; + + // THEN + expect(when).toThrowError('Static function `implementInterface` requires an interfaceType to implement'); + }); + + test('ObjectType can implement from interface types', () => { // WHEN const baseTest = new appsync.InterfaceType('baseTest', { definition: { id: t.id, }, }); - const test = appsync.InterfaceType.extendInterface('Test', { - interfaceType: baseTest, + const objectTest = appsync.ObjectType.implementInterface('objectTest', { + interfaceTypes: [baseTest], definition: { id2: t.id, }, + directives: [appsync.Directive.custom('@test')], + }); + + api.appendToSchema(baseTest.toString()); + api.appendToSchema(objectTest.toString()); + const gql_interface = 'interface baseTest {\n id: ID\n}\n'; + const gql_object = 'type objectTest implements baseTest @test {\n id2: ID\n id: ID\n}\n'; + const out = `${gql_interface}${gql_object}`; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('ObjectType can implement from multiple interface types', () => { + // WHEN + const baseTest = new appsync.InterfaceType('baseTest', { + definition: { id: t.id }, + }); + const anotherTest = new appsync.InterfaceType('anotherTest', { + definition: { id2: t.id }, }); const objectTest = appsync.ObjectType.implementInterface('objectTest', { - interfaceType: test, + interfaceTypes: [anotherTest, baseTest], definition: { id3: t.id, }, - directives: [appsync.Directive.custom('@test')], }); - api.appendToSchema(test.toString()); + api.appendToSchema(baseTest.toString()); + api.appendToSchema(anotherTest.toString()); api.appendToSchema(objectTest.toString()); - const gql_interface = 'interface Test implements baseTest {\n id2: ID\n id: ID\n}\n'; - const gql_object = 'type objectTest implements Test @test {\n id3: ID\n id2: ID\n id: ID\n}\n'; + + const gql_interface = 'interface baseTest {\n id: ID\n}\ninterface anotherTest {\n id2: ID\n}\n'; + const gql_object = 'type objectTest implements anotherTest, baseTest {\n id3: ID\n id2: ID\n id: ID\n}\n'; const out = `${gql_interface}${gql_object}`; // THEN From 6511d861320fa3d5721328d9143a29713cb198cd Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 14 Aug 2020 14:58:38 -0700 Subject: [PATCH 42/78] fix a bug in implementation --- packages/@aws-cdk/aws-appsync/lib/schema-types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 427f984b90a5b..8195b6e78cb8a 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -328,7 +328,7 @@ export interface IntermediateTypeProps { /** * the attributes of this type */ - readonly definition: { [key: string]: GraphqlType }; + readonly definition: { [key: string]: GraphqlType | ResolvableField }; } /** @@ -491,7 +491,8 @@ export class ObjectType extends InterfaceType { let schemaAddition = `type ${title} ${directives}{\n`; Object.keys(this.definition).forEach( (key) => { const attribute = this.definition[key]; - schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + const args = attribute instanceof ResolvableField ? attribute.argsToString() : ''; + schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; }); return `${schemaAddition}}`; } From beee2549f135cd9c54e2e48dceea5ccd4bc58ae9 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 14 Aug 2020 16:03:39 -0700 Subject: [PATCH 43/78] update code-first example on readme --- packages/@aws-cdk/aws-appsync/README.md | 125 +++++++++++++++++++----- 1 file changed, 103 insertions(+), 22 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 432f92bdbacb7..3f3d5c90d67c9 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -170,46 +170,127 @@ A code-first approach offers a developer workflow with: - **reusability**: simplifying down boilerplate/repetitive code - **consistency**: resolvers and schema definition will always be synced -The code-first approach allows for dynamic schema generation. You can generate your schema based on variables and templates to reduce code duplication. +The code-first approach allows for **dynamic** schema generation. You can generate your schema based on variables and templates to reduce code duplication. #### Code-First Example -We are going to reference the [example](#Example) through a code-first approach. +To showcase the code-first approach. Let's try to model the following schema segment. + +```gql +interface Node { + id: String +} + +type Query { + allFilms(after: String, first: Int, before: String, last: Int): FilmConnection +} + +type FilmNode implements Node { + filmName: String +} + +type FilmConnection { + edges: [FilmEdge] + films: [Film] + totalCount: Int +} + +type FilmEdge { + node: Film + cursor: String +} +``` + +Above we see a schema that allows for generating paginated responses. For example, +we can query `allFilms(first: 100)` since `FilmConnection` acts as an intermediary +for holding `FilmEdges` we can write a resolver to return the first 100 films. + +In a separate file, we can declare our scalar types: `scalar-types.ts`. + +```ts +import { GraphqlType } from '@aws-cdk/aws-appsync'; + +export const string = appsync.GraphqlType.string(); +export const int = appsync.GraphqlType.int(); +``` + +In another separate file, we can declare our object types and related functions. +We will call this file `object-types.ts` and we will have created it in a way that +allows us to generate other `XxxConnection` and `XxxEdges` in the future. ```ts +const pluralize = require('pluralize'); +import * as scalar from './scalar-types.ts'; import * as appsync from '@aws-cdk/aws-appsync'; -import * as db from '@aws-cdk/aws-dynamodb'; + +export const args = { + after: scalar.string, + first: scalar.int, + before: scalar.string, + last: scalar.int, +}; + +export const Node = new appsync.InterfaceType('Node', { + definition: { id: scalar.string } +}); +export const FilmNode = new appsync.ObjectType.implementInterface('FilmNode', { + interfaceTypes: [Node], + definition: { filmName: scalar.string } +}); + +export function generateEdgeAndConnection(base: appsync.ObjectType) { + const edge = new appsync.ObjectType(`${base.name}Edge`, { + definition: { node: base.attribute(), cursor: scalar.string } + }); + const connection = new appsync.ObjectType(`${base.name}Connection`, { + definition: { + edges: edges.attribute({ isList: true }), + [pluralize(base.name)]: base.attribute({ isList: true }), + totalCount: scalar.int, + } + }); + return { edge: edge, connection: connection }; +} +``` + +Finally, we will go to our `cdk-stack` and combine everything together +to generate our schema. + +```ts +import * as appsync from '@aws-cdk/aws-appsync'; +import * as schema from './object-types'; const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', schemaDefinition: appsync.SchemaDefinition.CODE, - authorizationConfig: { - defaultAuthorization: { - authorizationType: appsync.AuthorizationType.IAM - }, - }, }); -const demoTable = new db.Table(stack, 'DemoTable', { - partitionKey: { - name: 'id', - type: db.AttributeType.STRING, - }, -}); - -const demoDS = api.addDynamoDbDataSource('demoDataSource', 'Table for Demos', demoTable); +this.objectTypes = [ schema.Node, schema.Film ]; -// Schema Definition starts here +const filmConnections = schema.generateEdgeAndConnection(schema.Film); -const demo = api.addType('demo', { +api.addType('Query', { definition: { - id: appsync.GraphqlType.string({ isRequired: true }), - version: appsync.GraphqlType.string({ isRequired: true }), - }, -}); + allFilms: new appsync.ResolvableField(filmConnections.connection.attribute(), dummyDataSource, { + args: schema.args, + requestMappingTemplate: dummyRequest, + responseMappingTemplate: dummyResponse, + }, + } + }); +}) +this.objectTypes.map((t) => api.appendToSchema(t)); +Object.keys(filmConnections).forEach((key) => api.appendToSchema(filmConnections[key])); ``` +Notice how we can utilize the `generateEdgeAndConnection` function to generate +Object Types. In the future, if we wanted to create more Object Types, we can simply +create the base Object Type (i.e. Film) and from there we can generate its respective +`Connections` and `Edges`. + +Check out a more in-depth example [here](https://github.com/BryanPan342/starwars-code-first). + #### GraphQL Types One of the benefits of GraphQL is its strongly typed nature. We define the From a45ab5c8db9aa0257034b41a6081b920620429d0 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 17 Aug 2020 15:26:03 -0700 Subject: [PATCH 44/78] make resolvable fields as part of graphql type --- .../@aws-cdk/aws-appsync/lib/schema-types.ts | 73 ++++++++--- .../@aws-cdk/aws-appsync/lib/schema-utils.ts | 121 +++++++++--------- 2 files changed, 120 insertions(+), 74 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts index 8195b6e78cb8a..d026bbc8658d4 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-types.ts @@ -1,6 +1,5 @@ -import { BaseDataSource } from './data-source'; import { Resolver } from './resolver'; -import { Directive, Type, ResolvableField, ResolvableFieldOptions } from './schema-utils'; +import { Directive, Type, ResolvableFieldOptions } from './schema-utils'; /** * Base options for GraphQL Types @@ -284,6 +283,13 @@ export class GraphqlType { */ public readonly isRequiredList: boolean; + /** + * The options to make this field resolvable + * + * @default - not a resolvable field + */ + public fieldOptions?: ResolvableFieldOptions; + /** * the intermediate type linked to this attribute * (i.e. an interface or an object) @@ -300,6 +306,26 @@ export class GraphqlType { this.intermediateType = options?.intermediateType; } + /** + * Make this field resolvable + * + * @param options the options to a field resolvable + * - dataSource + * - args + * - requestMappingTemplate + * - responseMappingTemplate + */ + public addResolvableField(options: ResolvableFieldOptions): GraphqlType{ + let type = new GraphqlType(this.type, { + isList: this.isList, + isRequired: this.isRequired, + isRequiredList: this.isRequiredList, + intermediateType: this.intermediateType, + }); + type.fieldOptions = options; + return type; + } + /** * Generate the string for this attribute */ @@ -314,6 +340,21 @@ export class GraphqlType { type = this.isRequiredList ? `${type}!` : type; return type; } + + /** + * Generate the arguments for this field + */ + public argsToString(): string { + if(this.fieldOptions) { + let args = '( '; + Object.keys(this.fieldOptions.args ?? {}).forEach((key) => { + const type = this.fieldOptions?.args?.[key].toString(); + args = `${args}${key}: ${type} `; + }); + return `${args})`; + } + return ''; + } } /** @@ -328,7 +369,7 @@ export interface IntermediateTypeProps { /** * the attributes of this type */ - readonly definition: { [key: string]: GraphqlType | ResolvableField }; + readonly definition: { [key: string]: GraphqlType }; } /** @@ -345,7 +386,7 @@ export class InterfaceType { /** * the attributes of this type */ - public readonly definition: { [key: string]: GraphqlType | ResolvableField }; + public readonly definition: { [key: string]: GraphqlType }; public constructor(name: string, props: IntermediateTypeProps) { this.name = name; @@ -453,8 +494,8 @@ export class ObjectType extends InterfaceType { Object.keys(this.definition).forEach((fieldName) => { const fieldInfo = this.definition[fieldName]; - if(fieldInfo instanceof ResolvableField) { - this.resolvers?.push(this.generateResolver(fieldName, fieldInfo)); + if(fieldInfo.fieldOptions) { + this.resolvers?.push(this.generateResolver(fieldName, fieldInfo.fieldOptions)); } }); } @@ -464,12 +505,12 @@ export class ObjectType extends InterfaceType { * * @param fieldName - The name of the resolvable field * @param type - the type for this resolvable field - * @param dataSource - the data source linked to this resolvable field - * @param options - the options for this resolvable field (args, requestMappingTemplate, responseMappingTemplate) + * @param options - the options for this resolvable field + * (dataSource, args, requestMappingTemplate, responseMappingTemplate) */ - public addResolvableField(fieldName: string, type: GraphqlType, dataSource: BaseDataSource, options?: ResolvableFieldOptions): Resolver{ - const resolvableField = new ResolvableField(type, dataSource, options); - const resolver = this.generateResolver(fieldName, resolvableField); + public addResolvableField(fieldName: string, type: GraphqlType, options: ResolvableFieldOptions): Resolver{ + const resolvableField = type.addResolvableField(options); + const resolver = this.generateResolver(fieldName, options); this.resolvers?.push(resolver); this.definition[fieldName] = resolvableField; return resolver; @@ -491,7 +532,7 @@ export class ObjectType extends InterfaceType { let schemaAddition = `type ${title} ${directives}{\n`; Object.keys(this.definition).forEach( (key) => { const attribute = this.definition[key]; - const args = attribute instanceof ResolvableField ? attribute.argsToString() : ''; + const args = attribute.argsToString(); schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; }); return `${schemaAddition}}`; @@ -516,12 +557,12 @@ export class ObjectType extends InterfaceType { /** * Generate the resolvers linked to this Object Type */ - protected generateResolver(fieldName: string, resolvableField: ResolvableField): Resolver{ - return resolvableField.dataSource.createResolver({ + protected generateResolver(fieldName: string, options: ResolvableFieldOptions): Resolver{ + return options.dataSource.createResolver({ typeName: this.name, fieldName: fieldName, - requestMappingTemplate: resolvableField.requestMappingTemplate, - responseMappingTemplate: resolvableField.responseMappingTemplate, + requestMappingTemplate: options.requestMappingTemplate, + responseMappingTemplate: options.responseMappingTemplate, }); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts b/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts index 7581a9aafaa5c..993a019f6e92b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts @@ -36,6 +36,7 @@ export class Directive { /** * Properties for configuring a resolvable field * + * @options dataSource - the data source linked to this resolvable field * @options args - the variables and types that define the arguments * * i.e. { string: GraphqlType, string: GraphqlType } @@ -44,39 +45,6 @@ export class Directive { * @options responseMappingTemplate - the mapping template for responses from this resolver */ export interface ResolvableFieldOptions { - /** - * The arguments for this resolvable field. - * - * i.e. type Example (first: String second: String) {} - * - where 'first' and 'second' are key values for args - * and 'String' is the GraphqlType - * - * @default - no arguments - */ - readonly args?: { [key: string]: GraphqlType }; - /** - * The request mapping template for this resolver - * - * @default - No mapping template - */ - readonly requestMappingTemplate?: MappingTemplate; - /** - * The response mapping template for this resolver - * - * @default - No mapping template - */ - readonly responseMappingTemplate?: MappingTemplate; -} - -/** - * Resolvable Fields build upon Graphql Types and provide fields - * that can resolve into operations on a data source. - */ -export class ResolvableField { - /** - * The return type for this field - */ - readonly graphqlType: GraphqlType; /** * The data source creating linked to this resolvable field */ @@ -103,34 +71,71 @@ export class ResolvableField { * @default - No mapping template */ readonly responseMappingTemplate?: MappingTemplate; +} - public constructor(type: GraphqlType, dataSource: BaseDataSource, options?: ResolvableFieldOptions) { - this.graphqlType = type; - this.dataSource = dataSource; - this.args = options?.args; - this.requestMappingTemplate = options?.requestMappingTemplate; - this.responseMappingTemplate = options?.responseMappingTemplate; - } +// /** +// * Resolvable Fields build upon Graphql Types and provide fields +// * that can resolve into operations on a data source. +// */ +// export class ResolvableField { +// /** +// * The return type for this field +// */ +// readonly graphqlType: GraphqlType; +// /** +// * The data source creating linked to this resolvable field +// */ +// readonly dataSource: BaseDataSource; +// /** +// * The arguments for this resolvable field. +// * +// * i.e. type Example (first: String second: String) {} +// * - where 'first' and 'second' are key values for args +// * and 'String' is the GraphqlType +// * +// * @default - no arguments +// */ +// readonly args?: { [key: string]: GraphqlType }; +// /** +// * The request mapping template for this resolver +// * +// * @default - No mapping template +// */ +// readonly requestMappingTemplate?: MappingTemplate; +// /** +// * The response mapping template for this resolver +// * +// * @default - No mapping template +// */ +// readonly responseMappingTemplate?: MappingTemplate; - /** - * Generate the string of this resolvable field - */ - public toString(): string{ - return this.graphqlType.toString(); - } +// public constructor(type: GraphqlType, dataSource: BaseDataSource, options?: ResolvableFieldOptions) { +// this.graphqlType = type; +// this.dataSource = dataSource; +// this.args = options?.args; +// this.requestMappingTemplate = options?.requestMappingTemplate; +// this.responseMappingTemplate = options?.responseMappingTemplate; +// } - /** - * Generate the args string of this resolvable field - */ - public argsToString(): string{ - let args = '( '; - Object.keys(this.args ?? {}).forEach((key) => { - const type = this.args?.[key].toString(); - args = `${args}${key}: ${type} `; - }); - return `${args})`; - } -} +// /** +// * Generate the string of this resolvable field +// */ +// public toString(): string{ +// return this.graphqlType.toString(); +// } + +// /** +// * Generate the args string of this resolvable field +// */ +// public argsToString(): string{ +// let args = '( '; +// Object.keys(this.args ?? {}).forEach((key) => { +// const type = this.args?.[key].toString(); +// args = `${args}${key}: ${type} `; +// }); +// return `${args})`; +// } +// } /** * Enum containing the Types that can be used to define ObjectTypes From 9a6fb99d1d2f06cc7b0ecfc5478ecfa49afe6d6f Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 17 Aug 2020 16:11:55 -0700 Subject: [PATCH 45/78] separate and create IField Interface --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 2 +- packages/@aws-cdk/aws-appsync/lib/index.ts | 5 +- .../@aws-cdk/aws-appsync/lib/schema-base.ts | 152 +++++++++++ .../lib/{schema-types.ts => schema-field.ts} | 244 ++++-------------- .../aws-appsync/lib/schema-intermediate.ts | 244 ++++++++++++++++++ .../@aws-cdk/aws-appsync/lib/schema-utils.ts | 227 ---------------- 6 files changed, 451 insertions(+), 423 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-base.ts rename packages/@aws-cdk/aws-appsync/lib/{schema-types.ts => schema-field.ts} (60%) create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts delete mode 100644 packages/@aws-cdk/aws-appsync/lib/schema-utils.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index ac11c4a98decf..6c6d068e38ede 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -4,7 +4,7 @@ import { ManagedPolicy, Role, ServicePrincipal, Grant, IGrantable } from '@aws-c import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; -import { ObjectType, ObjectTypeProps } from './schema-types'; +import { ObjectType, ObjectTypeProps } from './schema-intermediate'; /** * enum with all possible values for AppSync authorization type diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index 714e7ba699da9..341312977c5d3 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -4,7 +4,8 @@ export * from './key'; export * from './data-source'; export * from './mapping-template'; export * from './resolver'; -export * from './schema-types'; -export * from './schema-utils'; +export * from './schema-intermediate'; +export * from './schema-field'; +export * from './schema-base'; export * from './graphqlapi'; export * from './graphqlapi-base'; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts new file mode 100644 index 0000000000000..79682eacb9aea --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -0,0 +1,152 @@ +import { ResolvableFieldOptions } from './schema-field'; +import { InterfaceType } from './schema-intermediate'; + +/** + * A Graphql Field + */ +export interface IField { + /** + * the type of attribute + */ + readonly type: Type; + + /** + * property determining if this attribute is a list + * i.e. if true, attribute would be `[Type]` + * + * @default - false + */ + readonly isList: boolean; + + /** + * property determining if this attribute is non-nullable + * i.e. if true, attribute would be `Type!` and this attribute + * must always have a value + * + * @default - false + */ + readonly isRequired: boolean; + + /** + * property determining if this attribute is a non-nullable list + * i.e. if true, attribute would be `[ Type ]!` and this attribute's + * list must always have a value + * + * @default - false + */ + readonly isRequiredList: boolean; + + /** + * The options to make this field resolvable + * + * @default - not a resolvable field + */ + fieldOptions?: ResolvableFieldOptions; + + /** + * the intermediate type linked to this attribute + * (i.e. an interface or an object) + * + * @default - no intermediate type + */ + readonly intermediateType?: InterfaceType; + + /** + * Generate the string for this attribute + */ + toString(): string; + + /** + * Generate the arguments for this field + */ + argsToString(): 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 + */ + AWS_DATE = '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 + */ + AWS_TIME = '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 + */ + AWS_DATE_TIME = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWS_TIMESTAMP = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWS_EMAIL = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWS_JSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWS_URL = '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. + */ + AWS_PHONE = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWS_IP_ADDRESS = 'AWSIPAddress', + + /** + * Type used for Intermediate Types + * (i.e. an interface or an object type) + */ + INTERMEDIATE = 'INTERMEDIATE', +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts similarity index 60% rename from packages/@aws-cdk/aws-appsync/lib/schema-types.ts rename to packages/@aws-cdk/aws-appsync/lib/schema-field.ts index d026bbc8658d4..af2dbb2902732 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -1,5 +1,7 @@ -import { Resolver } from './resolver'; -import { Directive, Type, ResolvableFieldOptions } from './schema-utils'; +import { BaseDataSource } from './data-source'; +import { MappingTemplate } from './mapping-template'; +import { Type, IField } from './schema-base'; +import { InterfaceType } from './schema-intermediate'; /** * Base options for GraphQL Types @@ -64,7 +66,7 @@ export interface GraphqlTypeOptions extends BaseGraphqlTypeOptions { * * GraphQL Types are used to define the entirety of schema. */ -export class GraphqlType { +export class GraphqlType implements IField { /** * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. * @@ -298,7 +300,7 @@ export class GraphqlType { */ public readonly intermediateType?: InterfaceType; - private constructor(type: Type, options?: GraphqlTypeOptions) { + protected constructor(type: Type, options?: GraphqlTypeOptions) { this.type = type; this.isList = options?.isList ?? false; this.isRequired = options?.isRequired ?? false; @@ -315,15 +317,14 @@ export class GraphqlType { * - requestMappingTemplate * - responseMappingTemplate */ - public addResolvableField(options: ResolvableFieldOptions): GraphqlType{ - let type = new GraphqlType(this.type, { + public addResolvableField(options: ResolvableFieldOptions): ResolvableField{ + return new ResolvableField(this.type, { isList: this.isList, isRequired: this.isRequired, isRequiredList: this.isRequiredList, intermediateType: this.intermediateType, + fieldOptions: options, }); - type.fieldOptions = options; - return type; } /** @@ -345,224 +346,81 @@ export class GraphqlType { * Generate the arguments for this field */ public argsToString(): string { - if(this.fieldOptions) { - let args = '( '; - Object.keys(this.fieldOptions.args ?? {}).forEach((key) => { - const type = this.fieldOptions?.args?.[key].toString(); - args = `${args}${key}: ${type} `; - }); - return `${args})`; - } return ''; } } /** - * Properties for configuring an Intermediate Type + * Properties for configuring a resolvable field * - * @param definition - the variables and types that define this type - * i.e. { string: GraphqlType, string: GraphqlType } + * @options dataSource - the data source linked to this resolvable field + * @options args - the variables and types that define the arguments * - * @experimental - */ -export interface IntermediateTypeProps { - /** - * the attributes of this type - */ - readonly definition: { [key: string]: GraphqlType }; -} - -/** - * Interface Types are abstract types that includes a certain set of fields - * that other types must include if they implement the interface. + * i.e. { string: GraphqlType, string: GraphqlType } * - * @experimental + * @options requestMappingTemplate - the mapping template for requests to this resolver + * @options responseMappingTemplate - the mapping template for responses from this resolver */ -export class InterfaceType { +export interface ResolvableFieldOptions { /** - * the name of this type + * The data source creating linked to this resolvable field */ - public readonly name: string; + readonly dataSource: BaseDataSource; /** - * the attributes of this type + * The arguments for this resolvable field. + * + * i.e. type Example (first: String second: String) {} + * - where 'first' and 'second' are key values for args + * and 'String' is the GraphqlType + * + * @default - no arguments */ - public readonly definition: { [key: string]: GraphqlType }; - - public constructor(name: string, props: IntermediateTypeProps) { - this.name = name; - this.definition = props.definition; - } - + readonly args?: { [key: string]: GraphqlType }; /** - * Create an GraphQL Type representing this Intermediate Type + * The request mapping template for this resolver * - * @param options the options to configure this attribute - * - isList - * - isRequired - * - isRequiredList + * @default - No mapping template */ - public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ - return GraphqlType.intermediate({ - isList: options?.isList, - isRequired: options?.isRequired, - isRequiredList: options?.isRequiredList, - intermediateType: this, - }); - } - + readonly requestMappingTemplate?: MappingTemplate; /** - * Generate the string of this object type + * The response mapping template for this resolver + * + * @default - No mapping template */ - public toString(): string { - let schemaAddition = `interface ${this.name} {\n`; - Object.keys(this.definition).forEach( (key) => { - const attribute = this.definition[key]; - schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; - }); - return `${schemaAddition}}`; - } + readonly responseMappingTemplate?: MappingTemplate; } - /** - * Properties for configuring an Object Type - * - * @param definition - the variables and types that define this type - * i.e. { string: GraphqlType, string: GraphqlType } - * @param interfaceTypes - the interfaces that this object type implements - * @param directives - the directives for this object type + * Properties for configuring a resolvable field * - * @experimental + * @options fieldOptions - the properties to create this resolvable field */ -export interface ObjectTypeProps extends IntermediateTypeProps { +export interface ResolvableFieldProps extends GraphqlTypeOptions { /** - * The Interface Types this Object Type implements - * - * @default - no interface types - */ - readonly interfaceTypes?: InterfaceType[]; - /** - * the directives for this object type - * - * @default - no directives + * the properties to configure a resolvable field */ - readonly directives?: Directive[]; + readonly fieldOptions: ResolvableFieldOptions; } /** - * Object Types are types declared by you. - * - * @experimental + * Resolvable Fields build upon Graphql Types and provide fields + * that can resolve into operations on a data source. */ -export class ObjectType extends InterfaceType { - /** - * A method to define Object Types from an interface - */ - public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { - if (!props.interfaceTypes || !props.interfaceTypes.length) { - throw new Error('Static function `implementInterface` requires an interfaceType to implement'); - } - return new ObjectType(name, { - interfaceTypes: props.interfaceTypes, - definition: props.interfaceTypes.reduce((def, interfaceType) => { - return Object.assign({}, def, interfaceType.definition); - }, props.definition), - directives: props.directives, - }); - } - /** - * The Interface Types this Object Type implements - * - * @default - no interface types - */ - public readonly interfaceTypes?: InterfaceType[]; - /** - * the directives for this object type - * - * @default - no directives - */ - public readonly directives?: Directive[]; - /** - * The resolvers linked to this data source - */ - public resolvers?: Resolver[]; - - public constructor(name: string, props: ObjectTypeProps) { - super(name, props); - this.interfaceTypes = props.interfaceTypes; - this.directives = props.directives; - - Object.keys(this.definition).forEach((fieldName) => { - const fieldInfo = this.definition[fieldName]; - if(fieldInfo.fieldOptions) { - this.resolvers?.push(this.generateResolver(fieldName, fieldInfo.fieldOptions)); - } - }); - } - - /** - * Add a resolvable field to this Object Type - * - * @param fieldName - The name of the resolvable field - * @param type - the type for this resolvable field - * @param options - the options for this resolvable field - * (dataSource, args, requestMappingTemplate, responseMappingTemplate) - */ - public addResolvableField(fieldName: string, type: GraphqlType, options: ResolvableFieldOptions): Resolver{ - const resolvableField = type.addResolvableField(options); - const resolver = this.generateResolver(fieldName, options); - this.resolvers?.push(resolver); - this.definition[fieldName] = resolvableField; - return resolver; - } - - /** - * Generate the string of this object type - */ - public toString(): string { - let title = this.name; - if(this.interfaceTypes && this.interfaceTypes.length){ - title = `${title} implements`; - this.interfaceTypes.map((interfaceType) => { - title = `${title} ${interfaceType.name},`; - }); - title = title.slice(0, -1); - } - const directives = this.generateDirectives(this.directives); - let schemaAddition = `type ${title} ${directives}{\n`; - Object.keys(this.definition).forEach( (key) => { - const attribute = this.definition[key]; - const args = attribute.argsToString(); - schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; - }); - return `${schemaAddition}}`; - } - - /** - * Utility function to generate directives - * - * @param directives the directives of a given type - * @param delimiter the separator betweeen directives - * @default - ' ' - */ - private generateDirectives(directives?: Directive[], delimiter?: string): string{ - let schemaAddition = ''; - if (!directives){ return schemaAddition; } - directives.map((directive) => { - schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; - }); - return schemaAddition; +export class ResolvableField extends GraphqlType { + public constructor(type: Type, props: ResolvableFieldProps) { + super(type, props); + this.fieldOptions = props.fieldOptions; } /** - * Generate the resolvers linked to this Object Type + * Generate the args string of this resolvable field */ - protected generateResolver(fieldName: string, options: ResolvableFieldOptions): Resolver{ - return options.dataSource.createResolver({ - typeName: this.name, - fieldName: fieldName, - requestMappingTemplate: options.requestMappingTemplate, - responseMappingTemplate: options.responseMappingTemplate, + public argsToString(): string{ + let args = '( '; + Object.keys(this.fieldOptions?.args ?? {}).forEach((key) => { + const type = this.fieldOptions?.args?.[key].toString(); + args = `${args}${key}: ${type} `; }); + return `${args})`; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts new file mode 100644 index 0000000000000..63f76bad4e2b2 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -0,0 +1,244 @@ +import { Resolver } from './resolver'; +import { IField } from './schema-base'; +import { BaseGraphqlTypeOptions, GraphqlType, ResolvableFieldOptions } from './schema-field'; + +/** + * Directives for types + * + * i.e. @aws_iam or @aws_subscribe + */ +export class Directive { + /** + * Add the @aws_iam directive + */ + public static iam(): Directive{ + return new Directive('@aws_iam'); + } + + /** + * Add a custom directive + * + * @param statement - the directive statement to append + * Note: doesn't guarantee functionality + */ + public static custom(statement: string): Directive { + return new Directive(statement); + } + + /** + * the directive statement + */ + public readonly statement: string; + + private constructor(statement: string) { this.statement = statement; } +} + +/** + * Properties for configuring an Intermediate Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * + * @experimental + */ +export interface IntermediateTypeProps { + /** + * the attributes of this type + */ + readonly definition: { [key: string]: IField }; +} + +/** + * Interface Types are abstract types that includes a certain set of fields + * that other types must include if they implement the interface. + * + * @experimental + */ +export class InterfaceType { + /** + * the name of this type + */ + public readonly name: string; + /** + * the attributes of this type + */ + public readonly definition: { [key: string]: IField }; + + public constructor(name: string, props: IntermediateTypeProps) { + this.name = name; + this.definition = props.definition; + } + + /** + * Create an GraphQL Type representing this Intermediate Type + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ + return GraphqlType.intermediate({ + isList: options?.isList, + isRequired: options?.isRequired, + isRequiredList: options?.isRequiredList, + intermediateType: this, + }); + } + + /** + * Generate the string of this object type + */ + public toString(): string { + let schemaAddition = `interface ${this.name} {\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } +} + + +/** + * Properties for configuring an Object Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * @param interfaceTypes - the interfaces that this object type implements + * @param directives - the directives for this object type + * + * @experimental + */ +export interface ObjectTypeProps extends IntermediateTypeProps { + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + readonly interfaceTypes?: InterfaceType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + readonly directives?: Directive[]; +} + +/** + * Object Types are types declared by you. + * + * @experimental + */ +export class ObjectType extends InterfaceType { + /** + * A method to define Object Types from an interface + */ + public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { + if (!props.interfaceTypes || !props.interfaceTypes.length) { + throw new Error('Static function `implementInterface` requires an interfaceType to implement'); + } + return new ObjectType(name, { + interfaceTypes: props.interfaceTypes, + definition: props.interfaceTypes.reduce((def, interfaceType) => { + return Object.assign({}, def, interfaceType.definition); + }, props.definition), + directives: props.directives, + }); + } + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + public readonly interfaceTypes?: InterfaceType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + public readonly directives?: Directive[]; + /** + * The resolvers linked to this data source + */ + public resolvers?: Resolver[]; + + public constructor(name: string, props: ObjectTypeProps) { + super(name, props); + this.interfaceTypes = props.interfaceTypes; + this.directives = props.directives; + + Object.keys(this.definition).forEach((fieldName) => { + const fieldInfo = this.definition[fieldName]; + if(fieldInfo.fieldOptions) { + this.resolvers?.push(this.generateResolver(fieldName, fieldInfo.fieldOptions)); + } + }); + } + + /** + * Add a resolvable field to this Object Type + * + * @param fieldName - The name of the resolvable field + * @param type - the type for this resolvable field + * @param options - the options for this resolvable field + * (dataSource, args, requestMappingTemplate, responseMappingTemplate) + */ + public addResolvableField(fieldName: string, type: GraphqlType, options: ResolvableFieldOptions): Resolver{ + const resolvableField = type.addResolvableField(options); + const resolver = this.generateResolver(fieldName, options); + this.resolvers?.push(resolver); + this.definition[fieldName] = resolvableField; + return resolver; + } + + /** + * Generate the string of this object type + */ + public toString(): string { + let title = this.name; + if(this.interfaceTypes && this.interfaceTypes.length){ + title = `${title} implements`; + this.interfaceTypes.map((interfaceType) => { + title = `${title} ${interfaceType.name},`; + }); + title = title.slice(0, -1); + } + const directives = this.generateDirectives(this.directives); + let schemaAddition = `type ${title} ${directives}{\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + const args = attribute.argsToString(); + schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } + + /** + * Utility function to generate directives + * + * @param directives the directives of a given type + * @param delimiter the separator betweeen directives + * @default - ' ' + */ + private generateDirectives(directives?: Directive[], delimiter?: string): string{ + let schemaAddition = ''; + if (!directives){ return schemaAddition; } + directives.map((directive) => { + schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; + }); + return schemaAddition; + } + + /** + * Generate the resolvers linked to this Object Type + */ + protected generateResolver(fieldName: string, options: ResolvableFieldOptions): Resolver{ + return options.dataSource.createResolver({ + typeName: this.name, + fieldName: fieldName, + requestMappingTemplate: options.requestMappingTemplate, + responseMappingTemplate: options.responseMappingTemplate, + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts b/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts deleted file mode 100644 index 993a019f6e92b..0000000000000 --- a/packages/@aws-cdk/aws-appsync/lib/schema-utils.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { BaseDataSource } from './data-source'; -import { MappingTemplate } from './mapping-template'; -import { GraphqlType } from './schema-types'; - -/** - * Directives for types - * - * i.e. @aws_iam or @aws_subscribe - */ -export class Directive { - /** - * Add the @aws_iam directive - */ - public static iam(): Directive{ - return new Directive('@aws_iam'); - } - - /** - * Add a custom directive - * - * @param statement - the directive statement to append - * Note: doesn't guarantee functionality - */ - public static custom(statement: string): Directive { - return new Directive(statement); - } - - /** - * the directive statement - */ - public readonly statement: string; - - private constructor(statement: string) { this.statement = statement; } -} - -/** - * Properties for configuring a resolvable field - * - * @options dataSource - the data source linked to this resolvable field - * @options args - the variables and types that define the arguments - * - * i.e. { string: GraphqlType, string: GraphqlType } - * - * @options requestMappingTemplate - the mapping template for requests to this resolver - * @options responseMappingTemplate - the mapping template for responses from this resolver - */ -export interface ResolvableFieldOptions { - /** - * The data source creating linked to this resolvable field - */ - readonly dataSource: BaseDataSource; - /** - * The arguments for this resolvable field. - * - * i.e. type Example (first: String second: String) {} - * - where 'first' and 'second' are key values for args - * and 'String' is the GraphqlType - * - * @default - no arguments - */ - readonly args?: { [key: string]: GraphqlType }; - /** - * The request mapping template for this resolver - * - * @default - No mapping template - */ - readonly requestMappingTemplate?: MappingTemplate; - /** - * The response mapping template for this resolver - * - * @default - No mapping template - */ - readonly responseMappingTemplate?: MappingTemplate; -} - -// /** -// * Resolvable Fields build upon Graphql Types and provide fields -// * that can resolve into operations on a data source. -// */ -// export class ResolvableField { -// /** -// * The return type for this field -// */ -// readonly graphqlType: GraphqlType; -// /** -// * The data source creating linked to this resolvable field -// */ -// readonly dataSource: BaseDataSource; -// /** -// * The arguments for this resolvable field. -// * -// * i.e. type Example (first: String second: String) {} -// * - where 'first' and 'second' are key values for args -// * and 'String' is the GraphqlType -// * -// * @default - no arguments -// */ -// readonly args?: { [key: string]: GraphqlType }; -// /** -// * The request mapping template for this resolver -// * -// * @default - No mapping template -// */ -// readonly requestMappingTemplate?: MappingTemplate; -// /** -// * The response mapping template for this resolver -// * -// * @default - No mapping template -// */ -// readonly responseMappingTemplate?: MappingTemplate; - -// public constructor(type: GraphqlType, dataSource: BaseDataSource, options?: ResolvableFieldOptions) { -// this.graphqlType = type; -// this.dataSource = dataSource; -// this.args = options?.args; -// this.requestMappingTemplate = options?.requestMappingTemplate; -// this.responseMappingTemplate = options?.responseMappingTemplate; -// } - -// /** -// * Generate the string of this resolvable field -// */ -// public toString(): string{ -// return this.graphqlType.toString(); -// } - -// /** -// * Generate the args string of this resolvable field -// */ -// public argsToString(): string{ -// let args = '( '; -// Object.keys(this.args ?? {}).forEach((key) => { -// const type = this.args?.[key].toString(); -// args = `${args}${key}: ${type} `; -// }); -// return `${args})`; -// } -// } - -/** - * 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 - */ - AWS_DATE = '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 - */ - AWS_TIME = '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 - */ - AWS_DATE_TIME = 'AWSDateTime', - /** - * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. - * - * Timestamps are serialized and deserialized as numbers. - */ - AWS_TIMESTAMP = 'AWSTimestamp', - /** - * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) - */ - AWS_EMAIL = 'AWSEmail', - /** - * `AWSJson` scalar type represents a JSON string. - */ - AWS_JSON = 'AWSJSON', - /** - * `AWSURL` scalar type represetns a valid URL string. - * - * URLs wihtout schemes or contain double slashes are considered invalid. - */ - AWS_URL = '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. - */ - AWS_PHONE = 'AWSPhone', - /** - * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. - */ - AWS_IP_ADDRESS = 'AWSIPAddress', - - /** - * Type used for Intermediate Types - * (i.e. an interface or an object type) - */ - INTERMEDIATE = 'INTERMEDIATE', -} \ No newline at end of file From 72cad42bbdcade81359c5626e57700211befdf6d Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 17 Aug 2020 16:40:14 -0700 Subject: [PATCH 46/78] write test cases --- .../aws-appsync/lib/schema-intermediate.ts | 22 +++--- .../test/appsync-code-first.test.ts | 73 +++++++++++++++++++ 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index 63f76bad4e2b2..ab29cdc147ec3 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -177,19 +177,17 @@ export class ObjectType extends InterfaceType { } /** - * Add a resolvable field to this Object Type + * Add a field to this Object Type * - * @param fieldName - The name of the resolvable field - * @param type - the type for this resolvable field - * @param options - the options for this resolvable field - * (dataSource, args, requestMappingTemplate, responseMappingTemplate) - */ - public addResolvableField(fieldName: string, type: GraphqlType, options: ResolvableFieldOptions): Resolver{ - const resolvableField = type.addResolvableField(options); - const resolver = this.generateResolver(fieldName, options); - this.resolvers?.push(resolver); - this.definition[fieldName] = resolvableField; - return resolver; + * @param fieldName - The name of the field + * @param field - the field to add + */ + public addField(fieldName: string, field: IField): void { + if(field.fieldOptions){ + const resolver = this.generateResolver(fieldName, field.fieldOptions); + this.resolvers?.push(resolver); + } + this.definition[fieldName] = field; } /** diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 34e0eeaade11c..20a3e5d7aeb27 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -397,4 +397,77 @@ describe('testing Object Type properties', () => { Definition: `${out}`, }); }); + + test('Object Type can implement Resolvable Field in definition', () => { + // WHEN + const field = new appsync.ResolvableField(appsync.Type.STRING, { + fieldOptions: { + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + }, + }); + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + resolve: field, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve( arg: Int ): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can implement Resolvable Field from GraphqlType', () => { + // WHEN + const field = t.string.addResolvableField({ + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + }); + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + resolve: field, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve( arg: Int ): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can dynamically add Fields', () => { + // WHEN + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + }, + }); + test.addField('resolve', t.string.addResolvableField({ + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + })); + + test.addField('dynamic', t.string); + + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve( arg: Int ): String\n dynamic: String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); }); From e0bb9c06303fdc1f6ebb5d2f6477cbd3e1a23b94 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 18 Aug 2020 11:45:56 -0700 Subject: [PATCH 47/78] remove unneccessary static function --- .../aws-appsync/lib/schema-intermediate.ts | 22 ++++-------- .../test/appsync-code-first.test.ts | 34 ++----------------- 2 files changed, 8 insertions(+), 48 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index ab29cdc147ec3..6e5506700f263 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -131,21 +131,6 @@ export interface ObjectTypeProps extends IntermediateTypeProps { * @experimental */ export class ObjectType extends InterfaceType { - /** - * A method to define Object Types from an interface - */ - public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { - if (!props.interfaceTypes || !props.interfaceTypes.length) { - throw new Error('Static function `implementInterface` requires an interfaceType to implement'); - } - return new ObjectType(name, { - interfaceTypes: props.interfaceTypes, - definition: props.interfaceTypes.reduce((def, interfaceType) => { - return Object.assign({}, def, interfaceType.definition); - }, props.definition), - directives: props.directives, - }); - } /** * The Interface Types this Object Type implements * @@ -164,7 +149,12 @@ export class ObjectType extends InterfaceType { public resolvers?: Resolver[]; public constructor(name: string, props: ObjectTypeProps) { - super(name, props); + const options = { + definition: props.interfaceTypes?.reduce((def, interfaceType) => { + return Object.assign({}, def, interfaceType.definition); + }, props.definition) ?? props.definition, + }; + super(name, options); this.interfaceTypes = props.interfaceTypes; this.directives = props.directives; diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 20a3e5d7aeb27..9af3d0c35f730 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -290,36 +290,6 @@ describe('testing InterfaceType properties', () => { }); describe('testing Object Type properties', () => { - - test('errors when no InterfaceTypes are specified', () => { - // WHEN - const when = () => { - appsync.ObjectType.implementInterface('objectTest', { - definition: { - id2: t.id, - }, - }); - }; - - // THEN - expect(when).toThrowError('Static function `implementInterface` requires an interfaceType to implement'); - }); - - test('errors when implementing empty InterfaceTypes properties', () => { - // WHEN - const when = () => { - appsync.ObjectType.implementInterface('objectTest', { - interfaceTypes: [], - definition: { - id2: t.id, - }, - }); - }; - - // THEN - expect(when).toThrowError('Static function `implementInterface` requires an interfaceType to implement'); - }); - test('ObjectType can implement from interface types', () => { // WHEN const baseTest = new appsync.InterfaceType('baseTest', { @@ -327,7 +297,7 @@ describe('testing Object Type properties', () => { id: t.id, }, }); - const objectTest = appsync.ObjectType.implementInterface('objectTest', { + const objectTest = new appsync.ObjectType('objectTest', { interfaceTypes: [baseTest], definition: { id2: t.id, @@ -355,7 +325,7 @@ describe('testing Object Type properties', () => { const anotherTest = new appsync.InterfaceType('anotherTest', { definition: { id2: t.id }, }); - const objectTest = appsync.ObjectType.implementInterface('objectTest', { + const objectTest = new appsync.ObjectType('objectTest', { interfaceTypes: [anotherTest, baseTest], definition: { id3: t.id, From 3478a47d6dd2cb3789a8a0e678c445083e5b926d Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 18 Aug 2020 15:35:55 -0700 Subject: [PATCH 48/78] first pass changes --- .../@aws-cdk/aws-appsync/lib/schema-base.ts | 2 +- .../@aws-cdk/aws-appsync/lib/schema-field.ts | 98 +++++++++++++------ .../aws-appsync/lib/schema-intermediate.ts | 26 +++-- 3 files changed, 80 insertions(+), 46 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index 79682eacb9aea..d0d05da2911ca 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -41,7 +41,7 @@ export interface IField { * * @default - not a resolvable field */ - fieldOptions?: ResolvableFieldOptions; + readonly fieldOptions?: ResolvableFieldOptions; /** * the intermediate type linked to this attribute diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index af2dbb2902732..eb1f60661862c 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -285,13 +285,6 @@ export class GraphqlType implements IField { */ public readonly isRequiredList: boolean; - /** - * The options to make this field resolvable - * - * @default - not a resolvable field - */ - public fieldOptions?: ResolvableFieldOptions; - /** * the intermediate type linked to this attribute * (i.e. an interface or an object) @@ -351,21 +344,13 @@ export class GraphqlType implements IField { } /** - * Properties for configuring a resolvable field + * Properties for configuring a field * - * @options dataSource - the data source linked to this resolvable field * @options args - the variables and types that define the arguments * * i.e. { string: GraphqlType, string: GraphqlType } - * - * @options requestMappingTemplate - the mapping template for requests to this resolver - * @options responseMappingTemplate - the mapping template for responses from this resolver */ -export interface ResolvableFieldOptions { - /** - * The data source creating linked to this resolvable field - */ - readonly dataSource: BaseDataSource; +export interface FieldOptions { /** * The arguments for this resolvable field. * @@ -376,6 +361,64 @@ export interface ResolvableFieldOptions { * @default - no arguments */ readonly args?: { [key: string]: GraphqlType }; +} + +/** + * Properties for configuring a field + * + * @options fieldOptions - the properties to create this field + */ +export interface FieldProps extends GraphqlTypeOptions { + /** + * the properties to configure a field + */ + readonly fieldOptions: FieldOptions; +} + +/** + * Fields build upon Graphql Types and provide typing + * and arguments. + */ +export class Field extends GraphqlType implements IField { + /** + * The options for this field + * + * @default - no arguments + */ + public readonly fieldOptions?: ResolvableFieldOptions; + + public constructor(type: Type, props: FieldProps) { + super(type, props); + this.fieldOptions = props.fieldOptions; + } + + /** + * Generate the args string of this resolvable field + */ + public argsToString(): string{ + let args = '( '; + Object.keys(this.fieldOptions?.args ?? {}).forEach((key) => { + const type = this.fieldOptions?.args?.[key].toString(); + args = `${args}${key}: ${type} `; + }); + return `${args})`; + } +} + +/** + * Properties for configuring a resolvable field + * + * @options dataSource - the data source linked to this resolvable field + * @options requestMappingTemplate - the mapping template for requests to this resolver + * @options responseMappingTemplate - the mapping template for responses from this resolver + */ +export interface ResolvableFieldOptions extends FieldOptions { + /** + * The data source creating linked to this resolvable field + * + * @default - no data source + */ + readonly dataSource?: BaseDataSource; /** * The request mapping template for this resolver * @@ -406,21 +449,16 @@ export interface ResolvableFieldProps extends GraphqlTypeOptions { * Resolvable Fields build upon Graphql Types and provide fields * that can resolve into operations on a data source. */ -export class ResolvableField extends GraphqlType { +export class ResolvableField extends Field implements IField { + /** + * The options to make this field resolvable + * + * @default - not a resolvable field + */ + public readonly fieldOptions?: ResolvableFieldOptions; + public constructor(type: Type, props: ResolvableFieldProps) { super(type, props); this.fieldOptions = props.fieldOptions; } - - /** - * Generate the args string of this resolvable field - */ - public argsToString(): string{ - let args = '( '; - Object.keys(this.fieldOptions?.args ?? {}).forEach((key) => { - const type = this.fieldOptions?.args?.[key].toString(); - args = `${args}${key}: ${type} `; - }); - return `${args})`; - } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index dd8868909b733..9d5ffb417f73d 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -21,7 +21,6 @@ export class Directive { * Add a custom directive * * @param statement - the directive statement to append - * Note: doesn't guarantee functionality */ public static custom(statement: string): Directive { return new Directive(statement); @@ -161,9 +160,7 @@ export class ObjectType extends InterfaceType { Object.keys(this.definition).forEach((fieldName) => { const fieldInfo = this.definition[fieldName]; - if(fieldInfo.fieldOptions) { - this.resolvers?.push(this.generateResolver(fieldName, fieldInfo.fieldOptions)); - } + this.generateResolver(fieldName, fieldInfo.fieldOptions); }); } @@ -174,10 +171,7 @@ export class ObjectType extends InterfaceType { * @param field - the field to add */ public addField(fieldName: string, field: IField): void { - if(field.fieldOptions){ - const resolver = this.generateResolver(fieldName, field.fieldOptions); - this.resolvers?.push(resolver); - } + this.generateResolver(fieldName, field.fieldOptions); this.definition[fieldName] = field; } @@ -222,12 +216,14 @@ export class ObjectType extends InterfaceType { /** * Generate the resolvers linked to this Object Type */ - protected generateResolver(fieldName: string, options: ResolvableFieldOptions): Resolver{ - return options.dataSource.createResolver({ - typeName: this.name, - fieldName: fieldName, - requestMappingTemplate: options.requestMappingTemplate, - responseMappingTemplate: options.responseMappingTemplate, - }); + protected generateResolver(fieldName: string, options?: ResolvableFieldOptions): void { + if (options && options.dataSource){ + this.resolvers?.push(options.dataSource.createResolver({ + typeName: this.name, + fieldName: fieldName, + requestMappingTemplate: options.requestMappingTemplate, + responseMappingTemplate: options.responseMappingTemplate, + })); + } } } \ No newline at end of file From 1c549b53f5a3ca87b43ba1dc7931103d8f172697 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 18 Aug 2020 16:36:28 -0700 Subject: [PATCH 49/78] write tests and fix bugs --- .../@aws-cdk/aws-appsync/lib/schema-field.ts | 86 +++++++------------ .../aws-appsync/lib/schema-intermediate.ts | 30 +++++-- .../test/appsync-code-first.test.ts | 63 ++++++++++---- 3 files changed, 100 insertions(+), 79 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index eb1f60661862c..56c1cc03c7a06 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -12,7 +12,7 @@ import { InterfaceType } from './schema-intermediate'; * * @experimental */ -export interface BaseGraphqlTypeOptions { +export interface BaseTypeOptions { /** * property determining if this attribute is a list * i.e. if true, attribute would be [Type] @@ -49,7 +49,7 @@ export interface BaseGraphqlTypeOptions { * * @experimental */ -export interface GraphqlTypeOptions extends BaseGraphqlTypeOptions { +export interface GraphqlTypeOptions extends BaseTypeOptions { /** * the intermediate type linked to this attribute * @default - no intermediate type @@ -77,7 +77,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static id(options?: BaseGraphqlTypeOptions): GraphqlType { + public static id(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.ID, options); } /** @@ -88,7 +88,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static string(options?: BaseGraphqlTypeOptions): GraphqlType { + public static string(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.STRING, options); } /** @@ -99,7 +99,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static int(options?: BaseGraphqlTypeOptions): GraphqlType { + public static int(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.INT, options); } /** @@ -110,7 +110,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static float(options?: BaseGraphqlTypeOptions): GraphqlType { + public static float(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.FLOAT, options); } /** @@ -121,7 +121,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static boolean(options?: BaseGraphqlTypeOptions): GraphqlType { + public static boolean(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.BOOLEAN, options); } @@ -135,7 +135,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsDate(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsDate(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_DATE, options); } /** @@ -148,7 +148,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsTime(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsTime(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_TIME, options); } /** @@ -161,7 +161,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsDateTime(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsDateTime(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_DATE_TIME, options); } /** @@ -174,7 +174,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsTimestamp(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsTimestamp(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_TIMESTAMP, options); } /** @@ -185,7 +185,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsEmail(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsEmail(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_EMAIL, options); } /** @@ -196,7 +196,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsJson(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsJson(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_JSON, options); } /** @@ -209,7 +209,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsUrl(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsUrl(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_URL, options); } /** @@ -222,7 +222,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsPhone(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsPhone(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_PHONE, options); } /** @@ -233,7 +233,7 @@ export class GraphqlType implements IField { * - isRequired * - isRequiredList */ - public static awsIpAddress(options?: BaseGraphqlTypeOptions): GraphqlType { + public static awsIpAddress(options?: BaseTypeOptions): GraphqlType { return new GraphqlType(Type.AWS_IP_ADDRESS, options); } @@ -310,14 +310,8 @@ export class GraphqlType implements IField { * - requestMappingTemplate * - responseMappingTemplate */ - public addResolvableField(options: ResolvableFieldOptions): ResolvableField{ - return new ResolvableField(this.type, { - isList: this.isList, - isRequired: this.isRequired, - isRequiredList: this.isRequiredList, - intermediateType: this.intermediateType, - fieldOptions: options, - }); + public createResolvableField(options: ResolvableFieldOptions): ResolvableField{ + return new ResolvableField(this, options); } /** @@ -363,18 +357,6 @@ export interface FieldOptions { readonly args?: { [key: string]: GraphqlType }; } -/** - * Properties for configuring a field - * - * @options fieldOptions - the properties to create this field - */ -export interface FieldProps extends GraphqlTypeOptions { - /** - * the properties to configure a field - */ - readonly fieldOptions: FieldOptions; -} - /** * Fields build upon Graphql Types and provide typing * and arguments. @@ -387,20 +369,27 @@ export class Field extends GraphqlType implements IField { */ public readonly fieldOptions?: ResolvableFieldOptions; - public constructor(type: Type, props: FieldProps) { - super(type, props); - this.fieldOptions = props.fieldOptions; + public constructor(type: GraphqlType, options: FieldOptions) { + const props = { + isList: type.isList, + isRequired: type.isRequired, + isRequiredList: type.isRequiredList, + }; + super(type.type, props); + this.fieldOptions = options; } /** * Generate the args string of this resolvable field */ public argsToString(): string{ - let args = '( '; + if (!this.fieldOptions || !this.fieldOptions.args) { return ''; } + let args = '('; Object.keys(this.fieldOptions?.args ?? {}).forEach((key) => { const type = this.fieldOptions?.args?.[key].toString(); args = `${args}${key}: ${type} `; }); + args = args.slice(0, -1); return `${args})`; } } @@ -433,18 +422,6 @@ export interface ResolvableFieldOptions extends FieldOptions { readonly responseMappingTemplate?: MappingTemplate; } -/** - * Properties for configuring a resolvable field - * - * @options fieldOptions - the properties to create this resolvable field - */ -export interface ResolvableFieldProps extends GraphqlTypeOptions { - /** - * the properties to configure a resolvable field - */ - readonly fieldOptions: ResolvableFieldOptions; -} - /** * Resolvable Fields build upon Graphql Types and provide fields * that can resolve into operations on a data source. @@ -457,8 +434,9 @@ export class ResolvableField extends Field implements IField { */ public readonly fieldOptions?: ResolvableFieldOptions; - public constructor(type: Type, props: ResolvableFieldProps) { + public constructor(type: GraphqlType, options: ResolvableFieldOptions) { + const props = { args: options.args }; super(type, props); - this.fieldOptions = props.fieldOptions; + this.fieldOptions = options; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index 9d5ffb417f73d..43247830494f7 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -1,6 +1,6 @@ import { Resolver } from './resolver'; import { IField } from './schema-base'; -import { BaseGraphqlTypeOptions, GraphqlType, ResolvableFieldOptions } from './schema-field'; +import { BaseTypeOptions, GraphqlType, ResolvableFieldOptions } from './schema-field'; /** * Directives for types @@ -78,7 +78,7 @@ export class InterfaceType { * - isRequired * - isRequiredList */ - public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ + public attribute(options?: BaseTypeOptions): GraphqlType{ return GraphqlType.intermediate({ isList: options?.isList, isRequired: options?.isRequired, @@ -94,10 +94,21 @@ export class InterfaceType { let schemaAddition = `interface ${this.name} {\n`; Object.keys(this.definition).forEach( (key) => { const attribute = this.definition[key]; - schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + const args = attribute.argsToString(); + schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; }); return `${schemaAddition}}`; } + + /** + * Add a field to this Object Type + * + * @param fieldName - The name of the field + * @param field - the field to add + */ + public addField(fieldName: string, field: IField): void { + this.definition[fieldName] = field; + } } /** @@ -146,7 +157,7 @@ export class ObjectType extends InterfaceType { /** * The resolvers linked to this data source */ - public resolvers?: Resolver[]; + public resolvers: Resolver[]; public constructor(name: string, props: ObjectTypeProps) { const options = { @@ -157,10 +168,11 @@ export class ObjectType extends InterfaceType { super(name, options); this.interfaceTypes = props.interfaceTypes; this.directives = props.directives; + this.resolvers = []; Object.keys(this.definition).forEach((fieldName) => { - const fieldInfo = this.definition[fieldName]; - this.generateResolver(fieldName, fieldInfo.fieldOptions); + const field = this.definition[fieldName]; + this.generateResolver(fieldName, field.fieldOptions); }); } @@ -168,7 +180,7 @@ export class ObjectType extends InterfaceType { * Add a field to this Object Type * * @param fieldName - The name of the field - * @param field - the field to add + * @param field - the resolvable field to add */ public addField(fieldName: string, field: IField): void { this.generateResolver(fieldName, field.fieldOptions); @@ -217,8 +229,8 @@ export class ObjectType extends InterfaceType { * Generate the resolvers linked to this Object Type */ protected generateResolver(fieldName: string, options?: ResolvableFieldOptions): void { - if (options && options.dataSource){ - this.resolvers?.push(options.dataSource.createResolver({ + if (options?.dataSource){ + this.resolvers.push(options.dataSource.createResolver({ typeName: this.name, fieldName: fieldName, requestMappingTemplate: options.requestMappingTemplate, diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 9af3d0c35f730..72f8b09aaace7 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -270,6 +270,36 @@ describe('testing InterfaceType properties', () => { }); }); + test('InterfaceType fields can have arguments', () => { + // WHEN + baseTest.addField('test', new appsync.Field(t.string, { + args: { success: t.int }, + })); + api.appendToSchema(baseTest.toString()); + const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('InterfaceType fields will not produce resolvers', () => { + // WHEN + baseTest.addField('test', new appsync.ResolvableField(t.string, { + args: { success: t.int }, + dataSource: api.addNoneDataSource('none'), + })); + api.appendToSchema(baseTest.toString()); + const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + expect(stack).not.toHaveResource('AWS::AppSync::Resolver'); + }); + test('Interface Type can be a Graphql Type', () => { // WHEN const graphqlType = baseTest.attribute(); @@ -370,12 +400,10 @@ describe('testing Object Type properties', () => { test('Object Type can implement Resolvable Field in definition', () => { // WHEN - const field = new appsync.ResolvableField(appsync.Type.STRING, { - fieldOptions: { - dataSource: api.addNoneDataSource('none'), - args: { - arg: t.int, - }, + const field = new appsync.ResolvableField(t.string, { + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, }, }); const test = new appsync.ObjectType('Test', { @@ -385,7 +413,7 @@ describe('testing Object Type properties', () => { }, }); api.appendToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve( arg: Int ): String\n}\n'; + const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { @@ -395,7 +423,7 @@ describe('testing Object Type properties', () => { test('Object Type can implement Resolvable Field from GraphqlType', () => { // WHEN - const field = t.string.addResolvableField({ + const field = t.string.createResolvableField({ dataSource: api.addNoneDataSource('none'), args: { arg: t.int, @@ -408,7 +436,7 @@ describe('testing Object Type properties', () => { }, }); api.appendToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve( arg: Int ): String\n}\n'; + const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { @@ -418,26 +446,29 @@ describe('testing Object Type properties', () => { test('Object Type can dynamically add Fields', () => { // WHEN + const field = new appsync.ResolvableField(t.string, { + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + }); const test = new appsync.ObjectType('Test', { definition: { test: t.string, + resolve: field, }, }); - test.addField('resolve', t.string.addResolvableField({ - dataSource: api.addNoneDataSource('none'), - args: { - arg: t.int, - }, - })); + // test.addField('resolve', field); test.addField('dynamic', t.string); api.appendToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve( arg: Int ): String\n dynamic: String\n}\n'; + const out = 'type Test {\n test: String\n resolve(arg: Int): String\n dynamic: String\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { Definition: `${out}`, }); + expect(stack).toHaveResource('AWS::AppSync::Resolver'); }); }); From 988d2daefbd827f2a37e4c0048816c65527c17d6 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 18 Aug 2020 16:39:30 -0700 Subject: [PATCH 50/78] remove unneccessary function --- packages/@aws-cdk/aws-appsync/lib/schema-field.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index 56c1cc03c7a06..7f680e7f20ec0 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -301,19 +301,6 @@ export class GraphqlType implements IField { this.intermediateType = options?.intermediateType; } - /** - * Make this field resolvable - * - * @param options the options to a field resolvable - * - dataSource - * - args - * - requestMappingTemplate - * - responseMappingTemplate - */ - public createResolvableField(options: ResolvableFieldOptions): ResolvableField{ - return new ResolvableField(this, options); - } - /** * Generate the string for this attribute */ From 2f42e6489ae7a82e00bcb113d26f118185ed03cc Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 18 Aug 2020 16:42:45 -0700 Subject: [PATCH 51/78] bug fix --- packages/@aws-cdk/aws-appsync/lib/schema-field.ts | 1 + .../@aws-cdk/aws-appsync/test/appsync-code-first.test.ts | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index 7f680e7f20ec0..a5eecd00d6950 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -361,6 +361,7 @@ export class Field extends GraphqlType implements IField { isList: type.isList, isRequired: type.isRequired, isRequiredList: type.isRequiredList, + intermediateType: type.intermediateType, }; super(type.type, props); this.fieldOptions = options; diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 72f8b09aaace7..06ed5e286dc2c 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -423,7 +423,7 @@ describe('testing Object Type properties', () => { test('Object Type can implement Resolvable Field from GraphqlType', () => { // WHEN - const field = t.string.createResolvableField({ + const field = new appsync.ResolvableField(t.string, { dataSource: api.addNoneDataSource('none'), args: { arg: t.int, @@ -455,10 +455,9 @@ describe('testing Object Type properties', () => { const test = new appsync.ObjectType('Test', { definition: { test: t.string, - resolve: field, }, }); - + test.addField('resolve', field); // test.addField('resolve', field); test.addField('dynamic', t.string); From f938da8f944ff04e9166512524f1386160c7d649 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 18 Aug 2020 16:49:55 -0700 Subject: [PATCH 52/78] update test --- .../test/appsync-code-first.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 06ed5e286dc2c..6d97fb3e227c1 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -470,4 +470,36 @@ describe('testing Object Type properties', () => { }); expect(stack).toHaveResource('AWS::AppSync::Resolver'); }); + + test('Object Type can dynamically add Fields', () => { + // WHEN + const garbage = new appsync.InterfaceType('Garbage', { + definition: { + garbage: t.string, + }, + }); + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + }, + }); + const field = new appsync.ResolvableField(garbage.attribute(), { + dataSource: api.addNoneDataSource('none'), + args: { + arg: garbage.attribute(), + }, + }); + test.addField('resolve', field); + // test.addField('resolve', field); + test.addField('dynamic', garbage.attribute()); + + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve(arg: Garbage): Garbage\n dynamic: Garbage\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + expect(stack).toHaveResource('AWS::AppSync::Resolver'); + }); }); From 6eb781feca8a6f789bab5d2700a07218c1bffb9b Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 19 Aug 2020 15:29:16 -0700 Subject: [PATCH 53/78] update Readme and make interface for intermediate type --- packages/@aws-cdk/aws-appsync/README.md | 82 +++++++++++++++-- .../@aws-cdk/aws-appsync/lib/schema-base.ts | 91 ++++++++++++++++++- .../@aws-cdk/aws-appsync/lib/schema-field.ts | 7 +- .../aws-appsync/lib/schema-intermediate.ts | 41 +-------- 4 files changed, 170 insertions(+), 51 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index b81510773d7fe..36eaa15394a6c 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -180,7 +180,7 @@ api.grantMutation(role, 'updateExample'); api.grant(role, appsync.IamResource.ofType('Mutation', 'updateExample'), 'appsync:GraphQL'); ``` -### Code-First Schema +## Code-First Schema CDK offers the ability to generate your schema in a code-first approach. A code-first approach offers a developer workflow with: @@ -190,7 +190,7 @@ A code-first approach offers a developer workflow with: The code-first approach allows for **dynamic** schema generation. You can generate your schema based on variables and templates to reduce code duplication. -#### Code-First Example +### Code-First Example To showcase the code-first approach. Let's try to model the following schema segment. @@ -309,7 +309,7 @@ create the base Object Type (i.e. Film) and from there we can generate its respe Check out a more in-depth example [here](https://github.com/BryanPan342/starwars-code-first). -#### GraphQL Types +### GraphQL Types One of the benefits of GraphQL is its strongly typed nature. We define the types within an object, query, mutation, interface, etc. as **GraphQL Types**. @@ -325,9 +325,71 @@ More concretely, GraphQL Types are simply the types appended to variables. Referencing the object type `Demo` in the previous example, the GraphQL Types is `String!` and is applied to both the names `id` and `version`. -#### Intermediate Types +### Field and Resolvable Fields -Intermediate Types are abstractions above Scalar Types. They have a set of defined +While `GraphqlType` is a base implementation for GraphQL fields, we have abstractions +on top of `GraphqlType` that provide finer grain support. + +#### Field + +`Field` extends `GraphqlType` and will allow you to define arguments. [**Interface Types**](#Interface-Types) are not resolvable and this class will allow you to define arguments, +but not its resolvers. + +For example, if we want to create the following type: + +```gql +type Node { + test(argument: string): String +} +``` + +The CDK code required would be: + +```ts +const field = new appsync.Field(appsync.GraphqlType.string(), { + args: { + argument: appsync.GraphqlType.string(), + }, +}); +const type = new appsynce.ObjectType('Node', { + definition: { test: field }, +}); +``` + +#### Resolvable Fields + +`ResolvableField` extends `Field` and will allow you to define arguments and its resolvers. +[**Object Types**](#Object-Types) can have fields that resolve and perform operations on +your backend. + +For example, if we want to create the following type: + +```gql +type Query { + get(argument: string): String +} +``` + +The CDK code required would be: + +```ts +const field = new appsync.Field(appsync.GraphqlType.string(), { + args: { + argument: appsync.GraphqlType.string(), + }, + dataSource: api.addNoneDataSource('none'), + requestMappingTemplate: dummyRequest, + responseMappingTemplate: dummyResponse, +}); +const type = new appsynce.ObjectType('Query', { + definition: { get: field }, +}); +``` + + +### Intermediate Types + +Intermediate Types are defined by Graphql Types and Fields. They have a set of defined fields, where each field corresponds to another type in the system. Intermediate Types will be the meat of your GraphQL Schema as they are the types defined by you. @@ -335,7 +397,7 @@ Intermediate Types include: - [**Interface Types**](#Interface-Types) - [**Object Types**](#Object-Types) -#### Interface Types +### Interface Types **Interface Types** are abstract types that define the implementation of other intermediate types. They are useful for eliminating duplication and can be used @@ -350,7 +412,7 @@ const node = new appsync.InterfaceType('Node', { }); ``` -#### Object Types +### Object Types **Object Types** are types that you declare. For example, in the [code-first example](#code-first-example) the `demo` variable is an **Object Type**. **Object Types** are defined by @@ -378,7 +440,7 @@ You can create Object Types in three ways: `scalar-types.ts` - a file for scalar type definitions ```ts - export const required_string = new appsync.GraphqlType.string({ isRequired: true }); + export const required_string = appsync.GraphqlType.string({ isRequired: true }); ``` `object-types.ts` - a file for object type definitions @@ -405,7 +467,7 @@ You can create Object Types in three ways: id: appsync.GraphqlType.string({ isRequired: true }), }, }); - const demo = new appsync.ObjectType.implementInterface('Demo', { + const demo = new appsync.ObjectType('Demo', { interfaceTypes: [ node ], defintion: { version: appsync.GraphqlType.string({ isRequired: true }), @@ -428,4 +490,4 @@ You can create Object Types in three ways: }); ``` > This method provides easy use and is ideal for smaller projects. - \ No newline at end of file + diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index d0d05da2911ca..38e7ffd5eec11 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -1,3 +1,4 @@ +import { Resolver } from './resolver'; import { ResolvableFieldOptions } from './schema-field'; import { InterfaceType } from './schema-intermediate'; @@ -49,7 +50,7 @@ export interface IField { * * @default - no intermediate type */ - readonly intermediateType?: InterfaceType; + readonly intermediateType?: IIntermediateType; /** * Generate the string for this attribute @@ -62,6 +63,94 @@ export interface IField { argsToString(): string; } +/** + * Intermediate Types are types that includes a certain set of fields + * that define the entirety of your schema + */ +export interface IIntermediateType { + /** + * the name of this type + */ + readonly name: string; + + /** + * the attributes of this type + */ + readonly definition: { [key: string]: IField }; + + /** + * The Interface Types this Intermediate Type implements + * + * @default - no interface types + */ + readonly interfaceTypes?: InterfaceType[]; + + /** + * the directives for this object type + * + * @default - no directives + */ + readonly directives?: Directive[]; + + /** + * The resolvers linked to this data source + */ + resolvers?: Resolver[]; + + /** + * the intermediate type linked to this attribute + * (i.e. an interface or an object) + * + * @default - no intermediate type + */ + readonly intermediateType?: InterfaceType; + + /** + * Generate the string of this object type + */ + toString(): string; + + /** + * Add a field to this Intermediate Type + * + * @param fieldName - The name of the field + * @param field - the resolvable field to add + */ + addField(fieldName: string, field: IField): void; +} + +/** + * Directives for types + * + * i.e. @aws_iam or @aws_subscribe + * + * @experimental + */ +export class Directive { + /** + * Add the @aws_iam directive + */ + public static iam(): Directive{ + return new Directive('@aws_iam'); + } + + /** + * Add a custom directive + * + * @param statement - the directive statement to append + */ + public static custom(statement: string): Directive { + return new Directive(statement); + } + + /** + * the directive statement + */ + public readonly statement: string; + + private constructor(statement: string) { this.statement = statement; } +} + /** * Enum containing the Types that can be used to define ObjectTypes */ diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index a5eecd00d6950..690c084524144 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -1,7 +1,6 @@ import { BaseDataSource } from './data-source'; import { MappingTemplate } from './mapping-template'; -import { Type, IField } from './schema-base'; -import { InterfaceType } from './schema-intermediate'; +import { Type, IField, IIntermediateType } from './schema-base'; /** * Base options for GraphQL Types @@ -54,7 +53,7 @@ export interface GraphqlTypeOptions extends BaseTypeOptions { * the intermediate type linked to this attribute * @default - no intermediate type */ - readonly intermediateType?: InterfaceType; + readonly intermediateType?: IIntermediateType; } /** @@ -291,7 +290,7 @@ export class GraphqlType implements IField { * * @default - no intermediate type */ - public readonly intermediateType?: InterfaceType; + public readonly intermediateType?: IIntermediateType; protected constructor(type: Type, options?: GraphqlTypeOptions) { this.type = type; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index 43247830494f7..924030f4cf244 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -1,39 +1,7 @@ import { Resolver } from './resolver'; -import { IField } from './schema-base'; +import { Directive, IField, IIntermediateType } from './schema-base'; import { BaseTypeOptions, GraphqlType, ResolvableFieldOptions } from './schema-field'; -/** - * Directives for types - * - * i.e. @aws_iam or @aws_subscribe - * - * @experimental - */ -export class Directive { - /** - * Add the @aws_iam directive - */ - public static iam(): Directive{ - return new Directive('@aws_iam'); - } - - /** - * Add a custom directive - * - * @param statement - the directive statement to append - */ - public static custom(statement: string): Directive { - return new Directive(statement); - } - - /** - * the directive statement - */ - public readonly statement: string; - - private constructor(statement: string) { this.statement = statement; } -} - /** * Properties for configuring an Intermediate Type * @@ -55,7 +23,7 @@ export interface IntermediateTypeProps { * * @experimental */ -export class InterfaceType { +export class InterfaceType implements IIntermediateType { /** * the name of this type */ @@ -141,7 +109,7 @@ export interface ObjectTypeProps extends IntermediateTypeProps { * * @experimental */ -export class ObjectType extends InterfaceType { +export class ObjectType extends InterfaceType implements IIntermediateType { /** * The Interface Types this Object Type implements * @@ -157,7 +125,7 @@ export class ObjectType extends InterfaceType { /** * The resolvers linked to this data source */ - public resolvers: Resolver[]; + public resolvers?: Resolver[]; public constructor(name: string, props: ObjectTypeProps) { const options = { @@ -230,6 +198,7 @@ export class ObjectType extends InterfaceType { */ protected generateResolver(fieldName: string, options?: ResolvableFieldOptions): void { if (options?.dataSource){ + if(!this.resolvers){ this.resolvers = []; } this.resolvers.push(options.dataSource.createResolver({ typeName: this.name, fieldName: fieldName, From 2d89379a2e1f298fd66fe263a499424daece8c1d Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 19 Aug 2020 15:35:47 -0700 Subject: [PATCH 54/78] remove spacing --- packages/@aws-cdk/aws-appsync/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 36eaa15394a6c..0b39a721cb9a7 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -386,7 +386,6 @@ const type = new appsynce.ObjectType('Query', { }); ``` - ### Intermediate Types Intermediate Types are defined by Graphql Types and Fields. They have a set of defined From 1100f1fbb79de0086775bb3b0b26c9b474028818 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 19 Aug 2020 15:37:08 -0700 Subject: [PATCH 55/78] add links --- packages/@aws-cdk/aws-appsync/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 0b39a721cb9a7..4c81b7ef56808 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -317,8 +317,8 @@ types within an object, query, mutation, interface, etc. as **GraphQL Types**. GraphQL Types are the building blocks of types, whether they are scalar, objects, interfaces, etc. GraphQL Types can be: - [**Scalar Types**](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html): Id, Int, String, AWSDate, etc. -- **Object Types**: types that you generate (i.e. `demo` from the example above) -- **Interface Types**: abstract types that define the base implementation of other +- [**Object Types**](#Object-Types): types that you generate (i.e. `demo` from the example above) +- [**Interface Types**](#Interface-Types): abstract types that define the base implementation of other Intermediate Types More concretely, GraphQL Types are simply the types appended to variables. From 57fcb030e4ee59c000f4bd4b82c0e0e8c6184279 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 20 Aug 2020 16:03:54 -0700 Subject: [PATCH 56/78] address issue --- packages/@aws-cdk/aws-appsync/README.md | 59 +++++++++++++++---- .../@aws-cdk/aws-appsync/lib/schema-base.ts | 6 +- .../@aws-cdk/aws-appsync/lib/schema-field.ts | 27 +++++---- .../test/appsync-code-first.test.ts | 18 ++++-- 4 files changed, 78 insertions(+), 32 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 4c81b7ef56808..ea98de8d32c4b 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -346,12 +346,13 @@ type Node { The CDK code required would be: ```ts -const field = new appsync.Field(appsync.GraphqlType.string(), { +const field = new appsync.Field({ + returnType: appsync.GraphqlType.string(), args: { argument: appsync.GraphqlType.string(), }, }); -const type = new appsynce.ObjectType('Node', { +const type = new appsync.InterfaceType('Node', { definition: { test: field }, }); ``` @@ -362,30 +363,62 @@ const type = new appsynce.ObjectType('Node', { [**Object Types**](#Object-Types) can have fields that resolve and perform operations on your backend. -For example, if we want to create the following type: +You can also create resolvable fields for object types. ```gql -type Query { - get(argument: string): String +type Info { + node(id: String): String } ``` The CDK code required would be: ```ts -const field = new appsync.Field(appsync.GraphqlType.string(), { - args: { - argument: appsync.GraphqlType.string(), +const info = new appsync.ObjectType('Info', { + definition: { + node: new appsync.ResolvableField({ + returnType: appsync.GraphqlType.string(), + args: { + id: appsync.GraphqlType.string(), + }, + dataSource: api.addNoneDataSource('none'), + requestMappingTemplate: dummyRequest, + responseMappingTemplate: dummyResponse, + }), }, - dataSource: api.addNoneDataSource('none'), - requestMappingTemplate: dummyRequest, - responseMappingTemplate: dummyResponse, }); -const type = new appsynce.ObjectType('Query', { - definition: { get: field }, +``` + +To nest resolvers, we can also create top level query types that call upon +other types. Building off the previous example, if we want the following graphql +type definition: + +```gql +type Query { + get(argument: string): Info +} +``` + +The CDK code required would be: + +```ts +const query = new appsync.ObjectType('Query', { + definition: { + get: new appsync.ResolvableField({ + returnType: appsync.GraphqlType.string(), + args: { + argument: appsync.GraphqlType.string(), + }, + dataSource: api.addNoneDataSource('none'), + requestMappingTemplate: dummyRequest, + responseMappingTemplate: dummyResponse, + }), + }, }); ``` +Learn more about fields and resolvers [here](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-overview.html). + ### Intermediate Types Intermediate Types are defined by Graphql Types and Fields. They have a set of defined diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index 38e7ffd5eec11..7ae338e6f0a38 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -15,7 +15,7 @@ export interface IField { * property determining if this attribute is a list * i.e. if true, attribute would be `[Type]` * - * @default - false + * @default false */ readonly isList: boolean; @@ -24,7 +24,7 @@ export interface IField { * i.e. if true, attribute would be `Type!` and this attribute * must always have a value * - * @default - false + * @default false */ readonly isRequired: boolean; @@ -33,7 +33,7 @@ export interface IField { * i.e. if true, attribute would be `[ Type ]!` and this attribute's * list must always have a value * - * @default - false + * @default false */ readonly isRequiredList: boolean; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index 690c084524144..2a1f1314a09c1 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -332,7 +332,11 @@ export class GraphqlType implements IField { */ export interface FieldOptions { /** - * The arguments for this resolvable field. + * The return type for this field + */ + readonly returnType: GraphqlType; + /** + * The arguments for this field. * * i.e. type Example (first: String second: String) {} * - where 'first' and 'second' are key values for args @@ -355,14 +359,14 @@ export class Field extends GraphqlType implements IField { */ public readonly fieldOptions?: ResolvableFieldOptions; - public constructor(type: GraphqlType, options: FieldOptions) { + public constructor(options: FieldOptions) { const props = { - isList: type.isList, - isRequired: type.isRequired, - isRequiredList: type.isRequiredList, - intermediateType: type.intermediateType, + isList: options.returnType.isList, + isRequired: options.returnType.isRequired, + isRequiredList: options.returnType.isRequiredList, + intermediateType: options.returnType.intermediateType, }; - super(type.type, props); + super(options.returnType.type, props); this.fieldOptions = options; } @@ -421,9 +425,12 @@ export class ResolvableField extends Field implements IField { */ public readonly fieldOptions?: ResolvableFieldOptions; - public constructor(type: GraphqlType, options: ResolvableFieldOptions) { - const props = { args: options.args }; - super(type, props); + public constructor(options: ResolvableFieldOptions) { + const props = { + returnType: options.returnType, + args: options.args, + }; + super(props); this.fieldOptions = options; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 6d97fb3e227c1..2fd35668f15e3 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -272,7 +272,8 @@ describe('testing InterfaceType properties', () => { test('InterfaceType fields can have arguments', () => { // WHEN - baseTest.addField('test', new appsync.Field(t.string, { + baseTest.addField('test', new appsync.Field({ + returnType: t.string, args: { success: t.int }, })); api.appendToSchema(baseTest.toString()); @@ -286,7 +287,8 @@ describe('testing InterfaceType properties', () => { test('InterfaceType fields will not produce resolvers', () => { // WHEN - baseTest.addField('test', new appsync.ResolvableField(t.string, { + baseTest.addField('test', new appsync.ResolvableField({ + returnType: t.string, args: { success: t.int }, dataSource: api.addNoneDataSource('none'), })); @@ -400,7 +402,8 @@ describe('testing Object Type properties', () => { test('Object Type can implement Resolvable Field in definition', () => { // WHEN - const field = new appsync.ResolvableField(t.string, { + const field = new appsync.ResolvableField({ + returnType: t.string, dataSource: api.addNoneDataSource('none'), args: { arg: t.int, @@ -423,7 +426,8 @@ describe('testing Object Type properties', () => { test('Object Type can implement Resolvable Field from GraphqlType', () => { // WHEN - const field = new appsync.ResolvableField(t.string, { + const field = new appsync.ResolvableField({ + returnType: t.string, dataSource: api.addNoneDataSource('none'), args: { arg: t.int, @@ -446,7 +450,8 @@ describe('testing Object Type properties', () => { test('Object Type can dynamically add Fields', () => { // WHEN - const field = new appsync.ResolvableField(t.string, { + const field = new appsync.ResolvableField({ + returnType: t.string, dataSource: api.addNoneDataSource('none'), args: { arg: t.int, @@ -483,7 +488,8 @@ describe('testing Object Type properties', () => { test: t.string, }, }); - const field = new appsync.ResolvableField(garbage.attribute(), { + const field = new appsync.ResolvableField({ + returnType: garbage.attribute(), dataSource: api.addNoneDataSource('none'), args: { arg: garbage.attribute(), From 11d0f3d0314e46dc42c7fed1a200243bf81d257a Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 20 Aug 2020 16:13:29 -0700 Subject: [PATCH 57/78] lint --- packages/@aws-cdk/aws-appsync/lib/schema-base.ts | 2 +- packages/@aws-cdk/aws-appsync/lib/schema-field.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index 7ae338e6f0a38..c3f67fe46a595 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -130,7 +130,7 @@ export class Directive { /** * Add the @aws_iam directive */ - public static iam(): Directive{ + public static iam(): Directive { return new Directive('@aws_iam'); } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index 95000f688f395..703b49f6f2b6b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -373,7 +373,7 @@ export class Field extends GraphqlType implements IField { /** * Generate the args string of this resolvable field */ - public argsToString(): string{ + public argsToString(): string { if (!this.fieldOptions || !this.fieldOptions.args) { return ''; } let args = '('; Object.keys(this.fieldOptions?.args ?? {}).forEach((key) => { From b804c151193e37a6606483d5460337edbae170f0 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 21 Aug 2020 14:53:21 -0700 Subject: [PATCH 58/78] separating schema from graphql api --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 82 ++-------- packages/@aws-cdk/aws-appsync/lib/index.ts | 1 + packages/@aws-cdk/aws-appsync/lib/schema.ts | 142 ++++++++++++++++++ .../aws-appsync/test/appsync-apikey.test.ts | 24 +-- .../test/appsync-code-first.test.ts | 2 +- .../aws-appsync/test/appsync-dynamodb.test.ts | 3 +- .../aws-appsync/test/appsync-grant.test.ts | 3 +- .../aws-appsync/test/appsync-http.test.ts | 3 +- .../aws-appsync/test/appsync-lambda.test.ts | 3 +- .../aws-appsync/test/appsync-none.test.ts | 3 +- .../aws-appsync/test/appsync-schema.test.ts | 41 +---- .../@aws-cdk/aws-appsync/test/appsync.test.ts | 63 ++------ .../aws-appsync/test/integ.api-import.ts | 3 +- .../aws-appsync/test/integ.graphql-iam.ts | 5 +- .../aws-appsync/test/integ.graphql-schema.ts | 2 +- .../aws-appsync/test/integ.graphql.ts | 5 +- 16 files changed, 192 insertions(+), 193 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/lib/schema.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 6155c41a3047b..f097ec610d080 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,9 +1,9 @@ -import { readFileSync } from 'fs'; import { IUserPool } from '@aws-cdk/aws-cognito'; import { ManagedPolicy, Role, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; +import { Schema } from './schema'; import { ObjectType, ObjectTypeProps } from './schema-intermediate'; /** @@ -201,21 +201,6 @@ 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 */ @@ -242,18 +227,12 @@ 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 schema.graphql file + * Schema.fromCode allows schema definition through CDK + * Schema.fromFile(filePath: string) allows schema definition through schema.graphql file * * @experimental */ - readonly schemaDefinition: SchemaDefinition; - /** - * File containing the GraphQL schema definition. You have to specify a definition or a file containing one. - * - * @default - Use schemaDefinition - */ - readonly schemaDefinitionFile?: string; + readonly schema: Schema; /** * A flag indicating whether or not X-Ray tracing is enabled for the GraphQL API. * @@ -391,9 +370,9 @@ export class GraphQLApi extends GraphqlApiBase { public readonly name: string; /** - * underlying CFN schema resource + * the schema attached to this api */ - public readonly schema: CfnGraphQLSchema; + public readonly schema: Schema; /** * the configured API key, if present @@ -402,7 +381,7 @@ export class GraphQLApi extends GraphqlApiBase { */ public readonly apiKey?: string; - private schemaMode: SchemaDefinition; + private _schema: CfnGraphQLSchema; private api: CfnGraphQLApi; private _apiKey?: CfnApiKey; @@ -461,8 +440,8 @@ export class GraphQLApi extends GraphqlApiBase { this.arn = this.api.attrArn; this.graphQlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; - this.schemaMode = props.schemaDefinition; - this.schema = this.defineSchema(props.schemaDefinitionFile); + this.schema = props.schema; + this._schema = this.schema.bind(this); if (defaultAuthorizationType === AuthorizationType.API_KEY || props.authorizationConfig?.additionalAuthorizationModes?.some( @@ -477,7 +456,7 @@ export class GraphQLApi extends GraphqlApiBase { return mode.authorizationType === AuthorizationType.API_KEY && mode.apiKeyConfig; })?.apiKeyConfig; this._apiKey = this.createAPIKey(apiKeyConfig); - this._apiKey.addDependsOn(this.schema); + this._apiKey.addDependsOn(this._schema); this.apiKey = this._apiKey.attrApiKey; } } @@ -563,7 +542,7 @@ export class GraphQLApi extends GraphqlApiBase { * @param construct the dependee */ public addSchemaDependency(construct: CfnResource): boolean { - construct.addDependsOn(this.schema); + construct.addDependsOn(this._schema); return true; } @@ -636,29 +615,6 @@ export class GraphQLApi extends GraphqlApiBase { return authModes ? this.formatAdditionalAuthorizationModes(authModes) : undefined; } - /** - * 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, - }); - } - /** * Escape hatch to append to Schema as desired. Will always result * in a newline. @@ -670,11 +626,7 @@ export class GraphQLApi extends GraphqlApiBase { * @experimental */ public appendToSchema(addition: string, delimiter?: string): void { - if ( this.schemaMode != SchemaDefinition.CODE ) { - throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); - } - const sep = delimiter ?? ''; - this.schema.definition = `${this.schema.definition}${sep}${addition}\n`; + this.schema.appendToSchema(addition, delimiter); } /** @@ -686,14 +638,6 @@ export class GraphQLApi extends GraphqlApiBase { * @experimental */ public addType(name: string, props: ObjectTypeProps): ObjectType { - if ( this.schemaMode != SchemaDefinition.CODE ) { - throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); - }; - const type = new ObjectType(name, { - definition: props.definition, - directives: props.directives, - }); - this.appendToSchema(type.toString()); - return type; + return this.schema.addType(name, props); } } diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index 341312977c5d3..c4e05e9a5300e 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -4,6 +4,7 @@ export * from './key'; export * from './data-source'; export * from './mapping-template'; export * from './resolver'; +export * from './schema'; export * from './schema-intermediate'; export * from './schema-field'; export * from './schema-base'; 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..989ff669be215 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -0,0 +1,142 @@ +import { readFileSync } from 'fs'; +import { Lazy } from '@aws-cdk/core'; +import { CfnGraphQLSchema } from './appsync.generated'; +import { GraphQLApi } from './graphqlapi'; +import { ObjectTypeProps, ObjectType } from './schema-intermediate'; + +/** + * The Schema for a GraphQL Api + */ +export abstract class Schema { + /** + * Generate a Schema code-first + * + * @returns `SchemaCode` with mutable schema defintion + */ + public static fromCode(): SchemaCode { + return new SchemaCode(); + } + /** + * Generate a Schema from file + * + * @returns `SchemaFile` with immutable schema defintion + * @param filePath the file path of the schema file + */ + public static fromFile(filePath: string): SchemaFile { + return new SchemaFile(filePath); + } + + protected schema?: CfnGraphQLSchema; + + /** + * Called when the GraphQL Api is initialized to allow this object to bind + * to the stack. + * + * @param api The binding GraphQL Api + */ + public abstract bind(api: GraphQLApi): CfnGraphQLSchema; + + /** + * Escape hatch to append to Schema as desired. Will always result + * in a newline. + * + * @param addition the addition to add to schema + * @param delimiter the delimiter between schema and addition + * @default - '' + * + * @experimental + */ + public abstract appendToSchema(addition: string, delimiter?: string): void; + + /** + * Add an object type to the schema, if SchemaCode + * + * @param name the name of the object type + * @param props the definition + * + * @experimental + */ + public abstract addType(name: string, props: ObjectTypeProps): ObjectType; +} + +/** + * GraphQL Schema that is mutable through code-first approach + */ +export class SchemaCode extends Schema { + private definition: string; + constructor() { + super(); + this.definition = ''; + } + + public bind(api: GraphQLApi): CfnGraphQLSchema { + if (!this.schema) { + this.schema = new CfnGraphQLSchema(api, 'Schema', { + apiId: api.apiId, + definition: Lazy.stringValue({ produce: () => this.definition }), + }); + } + return this.schema; + } + + /** + * Escape hatch to append to Schema as desired. Will always result + * in a newline. + * + * @param addition the addition to add to schema + * @param delimiter the delimiter between schema and addition + * @default - '' + * + * @experimental + */ + public appendToSchema(addition: string, delimiter?: string): void { + const sep = delimiter ?? ''; + this.definition = `${this.definition}${sep}${addition}\n`; + } + + /** + * Add an object type to the schema + * + * @param name the name of the object type + * @param props the definition + * + * @experimental + */ + public addType(name: string, props: ObjectTypeProps): ObjectType { + const type = new ObjectType(name, { + definition: props.definition, + directives: props.directives, + }); + this.appendToSchema(type.toString()); + return type; + } +} + +/** + * GraphQL Schema that is declared through a schema-first approach + */ +export class SchemaFile extends Schema { + private filePath: string; + constructor(filePath: string) { + super(); + this.filePath = filePath; + } + + public bind(api: GraphQLApi): CfnGraphQLSchema { + if (!this.schema) { + this.schema = new CfnGraphQLSchema(api, 'Schema', { + apiId: api.apiId, + definition: readFileSync(this.filePath).toString('UTF-8'), + }); + } + return this.schema; + } + + public appendToSchema(_addition: string, _delimiter?: string): void { + throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); + } + + public addType(_name: string, _props: ObjectTypeProps): ObjectType { + throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); + } +} \ 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 0edba3c360ac9..c5490754f7f51 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-apikey.test.ts @@ -12,8 +12,7 @@ describe('AppSync Authorization Config', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), }); // THEN @@ -27,8 +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'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, @@ -50,8 +48,7 @@ describe('AppSync Authorization Config', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, @@ -70,8 +67,7 @@ describe('AppSync Authorization Config', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, @@ -91,8 +87,7 @@ describe('AppSync Authorization Config', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, @@ -120,8 +115,7 @@ describe('AppSync Authorization Config', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, @@ -157,8 +151,7 @@ describe('AppSync Authorization Config', () => { const when = () => { new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.API_KEY, @@ -182,8 +175,7 @@ describe('AppSync Authorization Config', () => { const when = () => { new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 34e0eeaade11c..186ec5d97cf88 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -10,7 +10,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.CODE, + schema: appsync.Schema.fromCode(), }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts index 0aca4e4ccc6c4..3fcc4ec2d9656 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts @@ -15,8 +15,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), }); }); 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 1b51183fe8abb..9d837fd7ea32f 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts @@ -15,8 +15,7 @@ beforeEach(() => { }); api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts index 0d5c653eae84f..bfdd66d37cbbb 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts @@ -11,8 +11,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), }); endpoint = 'aws.amazon.com'; }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts index 9b9113739bc54..57e837dcc69e0 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts @@ -11,8 +11,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts index f1f9faba9d09b..f2c25a46dd4ba 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts @@ -10,8 +10,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), }); }); 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 25542722f92e9..249db6c29d4de 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -21,7 +21,7 @@ describe('basic testing schema definition mode `code`', () => { // WHEN new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.CODE, + schema: appsync.Schema.fromCode(), }); // THEN @@ -34,7 +34,7 @@ describe('basic testing schema definition mode `code`', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.CODE, + schema: appsync.Schema.fromCode(), }); api.appendToSchema(type); api.appendToSchema(query); @@ -45,21 +45,6 @@ describe('basic testing schema definition mode `code`', () => { Definition: `${type}\n${query}\n${mutation}\n`, }); }); - - 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`', () => { @@ -68,8 +53,7 @@ describe('testing schema definition mode `file`', () => { // WHEN new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), }); // THEN @@ -78,25 +62,11 @@ describe('testing schema definition mode `file`', () => { }); }); - 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.'); - }); - test('definition mode `file` errors when addType is called', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), }); const when = () => { @@ -113,8 +83,7 @@ describe('testing schema definition mode `file`', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), }); const when = () => { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index d9faa4eeed03d..f69da04e99939 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -3,36 +3,19 @@ import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; -test('should not throw an Error', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const when = () => { - new appsync.GraphQLApi(stack, 'api', { - authorizationConfig: {}, - schemaDefinition: appsync.SchemaDefinition.FILE, - name: 'api', - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), - }); - }; - - // THEN - expect(when).not.toThrow(); -}); - -test('appsync should configure pipeline when pipelineConfig has contents', () => { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const api = new appsync.GraphQLApi(stack, 'api', { +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'api', { authorizationConfig: {}, name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), }); +}); +test('appsync should configure pipeline when pipelineConfig has contents', () => { + // WHEN new appsync.Resolver(stack, 'resolver', { api: api, typeName: 'test', @@ -48,17 +31,7 @@ test('appsync should configure pipeline when pipelineConfig has contents', () => }); test('appsync should configure resolver as unit when pipelineConfig is empty', () => { - // GIVEN - const stack = new cdk.Stack(); - // WHEN - const api = new appsync.GraphQLApi(stack, 'api', { - authorizationConfig: {}, - name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), - }); - new appsync.Resolver(stack, 'resolver', { api: api, typeName: 'test', @@ -72,17 +45,7 @@ test('appsync should configure resolver as unit when pipelineConfig is empty', ( }); test('appsync should configure resolver as unit when pipelineConfig is empty array', () => { - // GIVEN - const stack = new cdk.Stack(); - // WHEN - const api = new appsync.GraphQLApi(stack, 'api', { - authorizationConfig: {}, - name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), - }); - new appsync.Resolver(stack, 'resolver', { api: api, typeName: 'test', @@ -97,15 +60,11 @@ test('appsync should configure resolver as unit when pipelineConfig is empty arr }); test('when xray is enabled should not throw an Error', () => { - // GIVEN - const stack = new cdk.Stack(); - // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api-x-ray', { authorizationConfig: {}, name: 'api', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), xrayEnabled: true, }); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts index 74a8942246e51..c403e395a563b 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts @@ -24,8 +24,7 @@ const baseStack = new cdk.Stack(app, 'baseStack'); const baseApi = new appsync.GraphQLApi(baseStack, 'baseApi', { name: 'baseApi', - schemaDefinition: appsync.SchemaDefinition.FILE, - schemaDefinitionFile: path.join(__dirname, 'appsync.test.graphql'), + schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), }); const stack = new cdk.Stack(app, 'stack'); 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 5c6dced5c21e1..270b12fbf13b4 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts @@ -12,7 +12,7 @@ import { UserPoolDefaultAction, Values, IamResource, - SchemaDefinition, + Schema, } from '../lib'; /* @@ -38,8 +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'), + schema: Schema.fromFile(join(__dirname, 'integ.graphql-iam.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: AuthorizationType.USER_POOL, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index ad840e93384e8..bde6587a7765a 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -21,7 +21,7 @@ const stack = new cdk.Stack(app, 'code-first-schema'); const api = new appsync.GraphQLApi(stack, 'code-first-api', { name: 'api', - schemaDefinition: appsync.SchemaDefinition.CODE, + schema: appsync.Schema.fromCode(), }); const planet = ObjectType.planet; diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 607a495d9b735..458fa91813acd 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -8,9 +8,9 @@ import { KeyCondition, MappingTemplate, PrimaryKey, + Schema, UserPoolDefaultAction, Values, - SchemaDefinition, } from '../lib'; /* @@ -36,8 +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'), + schema: Schema.fromFile(join(__dirname, 'integ.graphql.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: AuthorizationType.USER_POOL, From d0fdddade762465208877348802349a5a2f342dc Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 21 Aug 2020 15:12:18 -0700 Subject: [PATCH 59/78] update readme --- packages/@aws-cdk/aws-appsync/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 96d0ce93d1879..f1188d3b06eff 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -47,8 +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'), + schema: appsync.Schema.fromFile(join(__dirname, 'schema.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM @@ -200,7 +199,7 @@ import * as db from '@aws-cdk/aws-dynamodb'; const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.CODE, + schema: appsync.Schema.fromCode(), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM @@ -281,7 +280,7 @@ You can create Object Types in three ways: ```ts const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.CODE, + schema: appsync.Schema.fromCode, }); const demo = new appsync.ObjectType('Demo', { defintion: { @@ -337,7 +336,7 @@ You can create Object Types in three ways: ```ts const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schemaDefinition: appsync.SchemaDefinition.CODE, + schema: appsync.Schema.fromCode(), }); api.addType('Demo', { defintion: { From 3d3fa2b52e8c1a133443564a2f5fa02bef97ccfb Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Fri, 21 Aug 2020 15:18:40 -0700 Subject: [PATCH 60/78] fix suggestions --- packages/@aws-cdk/aws-appsync/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index f1188d3b06eff..2bbc752688201 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -280,7 +280,7 @@ You can create Object Types in three ways: ```ts const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schema: appsync.Schema.fromCode, + schema: appsync.Schema.fromCode(), }); const demo = new appsync.ObjectType('Demo', { defintion: { From 911c87dace38fd6f784fc2f0a54854fab7765cf4 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 10:00:54 -0700 Subject: [PATCH 61/78] implement pipelineConfig + refactor --- .../@aws-cdk/aws-appsync/lib/schema-field.ts | 7 + .../aws-appsync/lib/schema-intermediate.ts | 1 + .../test/appsync-code-first.test.ts | 475 +----------------- .../test/appsync-interface-type.test.ts | 86 ++++ .../test/appsync-object-type.test.ts | 227 +++++++++ .../test/appsync-scalar-type.test.ts | 228 +++++++++ 6 files changed, 550 insertions(+), 474 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index 703b49f6f2b6b..ba22dd085fe56 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -399,6 +399,13 @@ export interface ResolvableFieldOptions extends FieldOptions { * @default - no data source */ readonly dataSource?: BaseDataSource; + /** + * configuration of the pipeline resolver + * + * @default - no pipeline resolver configuration + * An empty array or undefined prop will set resolver to be of type unit + */ + readonly pipelineConfig?: string[]; /** * The request mapping template for this resolver * diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index 1ea02dc28a3ef..4102e506d013f 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -202,6 +202,7 @@ export class ObjectType extends InterfaceType implements IIntermediateType { this.resolvers.push(options.dataSource.createResolver({ typeName: this.name, fieldName: fieldName, + pipelineConfig: options.pipelineConfig, requestMappingTemplate: options.requestMappingTemplate, responseMappingTemplate: options.responseMappingTemplate, })); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 2fd35668f15e3..8258bbf38010e 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -35,477 +35,4 @@ describe('testing addType for schema definition mode `code`', () => { }); }); -}); - -describe('testing all GraphQL Types', () => { - test('scalar type id', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.id, - }, - }); - const out = 'type Test {\n id: ID\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type string', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.string, - }, - }); - const out = 'type Test {\n id: String\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type int', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.int, - }, - }); - const out = 'type Test {\n id: Int\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type float', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.float, - }, - }); - const out = 'type Test {\n id: Float\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type boolean', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.boolean, - }, - }); - const out = 'type Test {\n id: Boolean\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSDate', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsDate, - }, - }); - const out = 'type Test {\n id: AWSDate\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSTime', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsTime, - }, - }); - const out = 'type Test {\n id: AWSTime\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSDateTime', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsDateTime, - }, - }); - const out = 'type Test {\n id: AWSDateTime\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSTimestamp', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsTimestamp, - }, - }); - const out = 'type Test {\n id: AWSTimestamp\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSEmail', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsEmail, - }, - }); - const out = 'type Test {\n id: AWSEmail\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSJSON', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsJson, - }, - }); - const out = 'type Test {\n id: AWSJSON\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - - test('scalar type AWSUrl', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsUrl, - }, - }); - const out = 'type Test {\n id: AWSURL\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSPhone', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsPhone, - }, - }); - const out = 'type Test {\n id: AWSPhone\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('scalar type AWSIPAddress', () => { - // WHEN - api.addType('Test', { - definition: { - id: t.awsIpAddress, - }, - }); - const out = 'type Test {\n id: AWSIPAddress\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); -}); - -describe('testing InterfaceType properties', () => { - let baseTest: appsync.InterfaceType; - beforeEach(()=>{ - baseTest = new appsync.InterfaceType('baseTest', { - definition: { - id: t.id, - }, - }); - }); - test('basic InterfaceType produces correct schema', () => { - // WHEN - api.appendToSchema(baseTest.toString()); - const out = 'interface baseTest {\n id: ID\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('InterfaceType fields can have arguments', () => { - // WHEN - baseTest.addField('test', new appsync.Field({ - returnType: t.string, - args: { success: t.int }, - })); - api.appendToSchema(baseTest.toString()); - const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('InterfaceType fields will not produce resolvers', () => { - // WHEN - baseTest.addField('test', new appsync.ResolvableField({ - returnType: t.string, - args: { success: t.int }, - dataSource: api.addNoneDataSource('none'), - })); - api.appendToSchema(baseTest.toString()); - const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - expect(stack).not.toHaveResource('AWS::AppSync::Resolver'); - }); - - test('Interface Type can be a Graphql Type', () => { - // WHEN - const graphqlType = baseTest.attribute(); - - const test = new appsync.ObjectType('Test', { - definition: { - test: graphqlType, - }, - }); - api.appendToSchema(test.toString()); - const out = 'type Test {\n test: baseTest\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); -}); - -describe('testing Object Type properties', () => { - test('ObjectType can implement from interface types', () => { - // WHEN - const baseTest = new appsync.InterfaceType('baseTest', { - definition: { - id: t.id, - }, - }); - const objectTest = new appsync.ObjectType('objectTest', { - interfaceTypes: [baseTest], - definition: { - id2: t.id, - }, - directives: [appsync.Directive.custom('@test')], - }); - - api.appendToSchema(baseTest.toString()); - api.appendToSchema(objectTest.toString()); - const gql_interface = 'interface baseTest {\n id: ID\n}\n'; - const gql_object = 'type objectTest implements baseTest @test {\n id2: ID\n id: ID\n}\n'; - const out = `${gql_interface}${gql_object}`; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('ObjectType can implement from multiple interface types', () => { - // WHEN - const baseTest = new appsync.InterfaceType('baseTest', { - definition: { id: t.id }, - }); - const anotherTest = new appsync.InterfaceType('anotherTest', { - definition: { id2: t.id }, - }); - const objectTest = new appsync.ObjectType('objectTest', { - interfaceTypes: [anotherTest, baseTest], - definition: { - id3: t.id, - }, - }); - - api.appendToSchema(baseTest.toString()); - api.appendToSchema(anotherTest.toString()); - api.appendToSchema(objectTest.toString()); - - const gql_interface = 'interface baseTest {\n id: ID\n}\ninterface anotherTest {\n id2: ID\n}\n'; - const gql_object = 'type objectTest implements anotherTest, baseTest {\n id3: ID\n id2: ID\n id: ID\n}\n'; - const out = `${gql_interface}${gql_object}`; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('Object Type can be a Graphql Type', () => { - // WHEN - const baseTest = new appsync.ObjectType('baseTest', { - definition: { - id: t.id, - }, - }); - const graphqlType = baseTest.attribute(); - const test = new appsync.ObjectType('Test', { - definition: { - test: graphqlType, - }, - }); - api.appendToSchema(test.toString()); - const out = 'type Test {\n test: baseTest\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('Object Type can implement Resolvable Field in definition', () => { - // WHEN - const field = new appsync.ResolvableField({ - returnType: t.string, - dataSource: api.addNoneDataSource('none'), - args: { - arg: t.int, - }, - }); - const test = new appsync.ObjectType('Test', { - definition: { - test: t.string, - resolve: field, - }, - }); - api.appendToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('Object Type can implement Resolvable Field from GraphqlType', () => { - // WHEN - const field = new appsync.ResolvableField({ - returnType: t.string, - dataSource: api.addNoneDataSource('none'), - args: { - arg: t.int, - }, - }); - const test = new appsync.ObjectType('Test', { - definition: { - test: t.string, - resolve: field, - }, - }); - api.appendToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - }); - - test('Object Type can dynamically add Fields', () => { - // WHEN - const field = new appsync.ResolvableField({ - returnType: t.string, - dataSource: api.addNoneDataSource('none'), - args: { - arg: t.int, - }, - }); - const test = new appsync.ObjectType('Test', { - definition: { - test: t.string, - }, - }); - test.addField('resolve', field); - // test.addField('resolve', field); - test.addField('dynamic', t.string); - - api.appendToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve(arg: Int): String\n dynamic: String\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - expect(stack).toHaveResource('AWS::AppSync::Resolver'); - }); - - test('Object Type can dynamically add Fields', () => { - // WHEN - const garbage = new appsync.InterfaceType('Garbage', { - definition: { - garbage: t.string, - }, - }); - const test = new appsync.ObjectType('Test', { - definition: { - test: t.string, - }, - }); - const field = new appsync.ResolvableField({ - returnType: garbage.attribute(), - dataSource: api.addNoneDataSource('none'), - args: { - arg: garbage.attribute(), - }, - }); - test.addField('resolve', field); - // test.addField('resolve', field); - test.addField('dynamic', garbage.attribute()); - - api.appendToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve(arg: Garbage): Garbage\n dynamic: Garbage\n}\n'; - - // THEN - expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { - Definition: `${out}`, - }); - expect(stack).toHaveResource('AWS::AppSync::Resolver'); - }); -}); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts new file mode 100644 index 0000000000000..f6b2c35d31c2c --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts @@ -0,0 +1,86 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; +import * as t from './scalar-type-defintions'; + +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); +}); + +describe('testing InterfaceType properties', () => { + let baseTest: appsync.InterfaceType; + beforeEach(()=>{ + baseTest = new appsync.InterfaceType('baseTest', { + definition: { + id: t.id, + }, + }); + }); + test('basic InterfaceType produces correct schema', () => { + // WHEN + api.appendToSchema(baseTest.toString()); + const out = 'interface baseTest {\n id: ID\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('InterfaceType fields can have arguments', () => { + // WHEN + baseTest.addField('test', new appsync.Field({ + returnType: t.string, + args: { success: t.int }, + })); + api.appendToSchema(baseTest.toString()); + const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('InterfaceType fields will not produce resolvers', () => { + // WHEN + baseTest.addField('test', new appsync.ResolvableField({ + returnType: t.string, + args: { success: t.int }, + dataSource: api.addNoneDataSource('none'), + })); + api.appendToSchema(baseTest.toString()); + const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + expect(stack).not.toHaveResource('AWS::AppSync::Resolver'); + }); + + test('Interface Type can be a Graphql Type', () => { + // WHEN + const graphqlType = baseTest.attribute(); + + const test = new appsync.ObjectType('Test', { + definition: { + test: graphqlType, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: baseTest\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts new file mode 100644 index 0000000000000..1d4359b735697 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts @@ -0,0 +1,227 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; +import * as t from './scalar-type-defintions'; + +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); +}); + +describe('testing Object Type properties', () => { + test('ObjectType can implement from interface types', () => { + // WHEN + const baseTest = new appsync.InterfaceType('baseTest', { + definition: { + id: t.id, + }, + }); + const objectTest = new appsync.ObjectType('objectTest', { + interfaceTypes: [baseTest], + definition: { + id2: t.id, + }, + directives: [appsync.Directive.custom('@test')], + }); + + api.appendToSchema(baseTest.toString()); + api.appendToSchema(objectTest.toString()); + const gql_interface = 'interface baseTest {\n id: ID\n}\n'; + const gql_object = 'type objectTest implements baseTest @test {\n id2: ID\n id: ID\n}\n'; + const out = `${gql_interface}${gql_object}`; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('ObjectType can implement from multiple interface types', () => { + // WHEN + const baseTest = new appsync.InterfaceType('baseTest', { + definition: { id: t.id }, + }); + const anotherTest = new appsync.InterfaceType('anotherTest', { + definition: { id2: t.id }, + }); + const objectTest = new appsync.ObjectType('objectTest', { + interfaceTypes: [anotherTest, baseTest], + definition: { + id3: t.id, + }, + }); + + api.appendToSchema(baseTest.toString()); + api.appendToSchema(anotherTest.toString()); + api.appendToSchema(objectTest.toString()); + + const gql_interface = 'interface baseTest {\n id: ID\n}\ninterface anotherTest {\n id2: ID\n}\n'; + const gql_object = 'type objectTest implements anotherTest, baseTest {\n id3: ID\n id2: ID\n id: ID\n}\n'; + const out = `${gql_interface}${gql_object}`; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can be a Graphql Type', () => { + // WHEN + const baseTest = new appsync.ObjectType('baseTest', { + definition: { + id: t.id, + }, + }); + const graphqlType = baseTest.attribute(); + const test = new appsync.ObjectType('Test', { + definition: { + test: graphqlType, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: baseTest\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can implement Resolvable Field in definition', () => { + // WHEN + const field = new appsync.ResolvableField({ + returnType: t.string, + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + }); + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + resolve: field, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can implement Resolvable Field from GraphqlType', () => { + // WHEN + const field = new appsync.ResolvableField({ + returnType: t.string, + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + }); + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + resolve: field, + }, + }); + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can implement Resolvable Field for pipelineResolvers', () => { + // WHEN + const test = new appsync.ObjectType('Test', { + definition: { + resolve: new appsync.ResolvableField({ + returnType: t.string, + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + pipelineConfig: ['test', 'test'], + }), + }, + }); + api.appendToSchema(test.toString()); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::Resolver', { + Kind: 'PIPELINE', + PipelineConfig: { Functions: ['test', 'test'] }, + }); + }); + + test('Object Type can dynamically add Fields', () => { + // WHEN + const field = new appsync.ResolvableField({ + returnType: t.string, + dataSource: api.addNoneDataSource('none'), + args: { + arg: t.int, + }, + }); + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + }, + }); + test.addField('resolve', field); + // test.addField('resolve', field); + test.addField('dynamic', t.string); + + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve(arg: Int): String\n dynamic: String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + expect(stack).toHaveResource('AWS::AppSync::Resolver'); + }); + + test('Object Type can dynamically add Fields', () => { + // WHEN + const garbage = new appsync.InterfaceType('Garbage', { + definition: { + garbage: t.string, + }, + }); + const test = new appsync.ObjectType('Test', { + definition: { + test: t.string, + }, + }); + const field = new appsync.ResolvableField({ + returnType: garbage.attribute(), + dataSource: api.addNoneDataSource('none'), + args: { + arg: garbage.attribute(), + }, + }); + test.addField('resolve', field); + // test.addField('resolve', field); + test.addField('dynamic', garbage.attribute()); + + api.appendToSchema(test.toString()); + const out = 'type Test {\n test: String\n resolve(arg: Garbage): Garbage\n dynamic: Garbage\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + expect(stack).toHaveResource('AWS::AppSync::Resolver'); + }); +}); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts new file mode 100644 index 0000000000000..fb8165384d8b6 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts @@ -0,0 +1,228 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; +import * as t from './scalar-type-defintions'; + +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schemaDefinition: appsync.SchemaDefinition.CODE, + }); +}); + +describe('testing all GraphQL Types', () => { + test('scalar type id', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.id, + }, + }); + const out = 'type Test {\n id: ID\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type string', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.string, + }, + }); + const out = 'type Test {\n id: String\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type int', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.int, + }, + }); + const out = 'type Test {\n id: Int\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type float', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.float, + }, + }); + const out = 'type Test {\n id: Float\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type boolean', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.boolean, + }, + }); + const out = 'type Test {\n id: Boolean\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSDate', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsDate, + }, + }); + const out = 'type Test {\n id: AWSDate\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSTime', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsTime, + }, + }); + const out = 'type Test {\n id: AWSTime\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSDateTime', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsDateTime, + }, + }); + const out = 'type Test {\n id: AWSDateTime\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSTimestamp', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsTimestamp, + }, + }); + const out = 'type Test {\n id: AWSTimestamp\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSEmail', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsEmail, + }, + }); + const out = 'type Test {\n id: AWSEmail\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSJSON', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsJson, + }, + }); + const out = 'type Test {\n id: AWSJSON\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + + test('scalar type AWSUrl', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsUrl, + }, + }); + const out = 'type Test {\n id: AWSURL\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSPhone', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsPhone, + }, + }); + const out = 'type Test {\n id: AWSPhone\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('scalar type AWSIPAddress', () => { + // WHEN + api.addType('Test', { + definition: { + id: t.awsIpAddress, + }, + }); + const out = 'type Test {\n id: AWSIPAddress\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); \ No newline at end of file From c23029fa41d79c897365afe6e24253bfe644f4c0 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 10:43:31 -0700 Subject: [PATCH 62/78] update readme --- packages/@aws-cdk/aws-appsync/README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 2bbc752688201..af14c2ee7c481 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -82,7 +82,18 @@ demoDS.createResolver({ }); ``` -## Imports +### Schema + +Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema` +for static convenience methods for various types of schema declaration. + +- `appsync.Schema.fromFile(filePath)` - specify a file in the local + filesystem which will be read as the schema +- `appsync.Schema.fromCode()` - configure schema declaration through a code-first + approach. See the [code-first schema](#Code-First-Schema) section for more + details. + +### Imports Any GraphQL Api that has been created outside the stack can be imported from another stack into your CDK app. Utilizing the `fromXxx` function, you have @@ -100,7 +111,7 @@ If you don't specify `graphqlArn` in `fromXxxAttributes`, CDK will autogenerate the expected `arn` for the imported api, given the `apiId`. For creating data sources and resolvers, an `apiId` is sufficient. -## Permissions +### Permissions When using `AWS_IAM` as the authorization type for GraphQL API, an IAM Role with correct permissions must be used for access to API. @@ -152,7 +163,7 @@ const api = new appsync.GraphQLApi(stack, 'API', { api.grant(role, appsync.IamResource.custom('types/Mutation/fields/updateExample'), 'appsync:GraphQL') ``` -### IamResource +#### IamResource In order to use the `grant` functions, you need to use the class `IamResource`. @@ -162,7 +173,7 @@ In order to use the `grant` functions, you need to use the class `IamResource`. - `IamResource.all()` permits ALL resources. -### Generic Permissions +#### Generic Permissions Alternatively, you can use more generic `grant` functions to accomplish the same usage. From 8d510fbbe2a4784d0c2573ee9d9307e316eb8b13 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 11:40:23 -0700 Subject: [PATCH 63/78] api changes --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 18 ++++++-- .../aws-appsync/lib/schema-intermediate.ts | 7 ++- packages/@aws-cdk/aws-appsync/lib/schema.ts | 44 +++++++++++++++---- .../test/appsync-code-first.test.ts | 32 +++++++------- .../aws-appsync/test/appsync-schema.test.ts | 21 +++++++-- .../test/integ.graphql-schema.expected.json | 2 +- .../aws-appsync/test/integ.graphql-schema.ts | 20 ++++++--- 7 files changed, 106 insertions(+), 38 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 040d6d2ca3e89..558c8154889ad 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -4,7 +4,7 @@ import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/c import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; import { Schema } from './schema'; -import { ObjectType, ObjectTypeProps } from './schema-intermediate'; +import { InterfaceType, IntermediateTypeProps, ObjectType, ObjectTypeProps } from './schema-intermediate'; /** * enum with all possible values for AppSync authorization type @@ -585,7 +585,19 @@ export class GraphQLApi extends GraphqlApiBase { * * @experimental */ - public addType(name: string, props: ObjectTypeProps): ObjectType { - return this.schema.addType(name, props); + public addObjectType(name: string, props: ObjectTypeProps): ObjectType { + return this.schema.addObjectType(name, props); + } + + /** + * Add an interface type to the schema + * + * @param name the name of the object type + * @param props the definition + * + * @experimental + */ + public addInterfaceType(name: string, props: IntermediateTypeProps): InterfaceType { + return this.schema.addInterfaceType(name, props); } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index bfe562333014a..0c1032de400a0 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -159,7 +159,12 @@ export class ObjectType extends InterfaceType { public readonly directives?: Directive[]; public constructor(name: string, props: ObjectTypeProps) { - super(name, props); + const options = { + definition: props.interfaceTypes?.reduce((def, interfaceType) => { + return Object.assign({}, def, interfaceType.definition); + }, props.definition) ?? props.definition, + }; + super(name, options); this.interfaceTypes = props.interfaceTypes; this.directives = props.directives; } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index 989ff669be215..e3bd0b54b0c2b 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -2,7 +2,7 @@ import { readFileSync } from 'fs'; import { Lazy } from '@aws-cdk/core'; import { CfnGraphQLSchema } from './appsync.generated'; import { GraphQLApi } from './graphqlapi'; -import { ObjectTypeProps, ObjectType } from './schema-intermediate'; +import { InterfaceType, IntermediateTypeProps, ObjectTypeProps, ObjectType } from './schema-intermediate'; /** * The Schema for a GraphQL Api @@ -56,7 +56,17 @@ export abstract class Schema { * * @experimental */ - public abstract addType(name: string, props: ObjectTypeProps): ObjectType; + public abstract addObjectType(name: string, props: ObjectTypeProps): ObjectType; + + /** + * Add an interface type to the schema + * + * @param name the name of the interface type + * @param props the definition + * + * @experimental + */ + public abstract addInterfaceType(name: string, props: IntermediateTypeProps): InterfaceType; } /** @@ -102,12 +112,27 @@ export class SchemaCode extends Schema { * * @experimental */ - public addType(name: string, props: ObjectTypeProps): ObjectType { + public addObjectType(name: string, props: ObjectTypeProps): ObjectType { const type = new ObjectType(name, { - definition: props.definition, - directives: props.directives, + ...props, + }); + this.appendToSchema(Lazy.stringValue({ produce: () => type.toString() })); + return type; + } + + /** + * Add an interface type to the schema + * + * @param name the name of the interface type + * @param props the definition + * + * @experimental + */ + public addInterfaceType(name: string, props: IntermediateTypeProps): InterfaceType { + const type = new InterfaceType(name, { + ...props, }); - this.appendToSchema(type.toString()); + this.appendToSchema(Lazy.stringValue({ produce: () => type.toString() })); return type; } } @@ -136,7 +161,10 @@ export class SchemaFile extends Schema { throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); } - public addType(_name: string, _props: ObjectTypeProps): ObjectType { - throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); + public addObjectType(_name: string, _props: ObjectTypeProps): ObjectType { + throw new Error('API cannot add object type because schema definition mode is not configured as CODE.'); + } + public addInterfaceType(_name: string, _props: IntermediateTypeProps): InterfaceType { + throw new Error('API cannot add interface type because schema definition mode is not configured as CODE.'); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 4a79a733907ae..8e991a3766b42 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -14,10 +14,10 @@ beforeEach(() => { }); }); -describe('testing addType for schema definition mode `code`', () => { +describe('testing addObjectType for schema definition mode `code`', () => { test('check scalar type id with all options', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.id, lid: t.list_id, @@ -40,7 +40,7 @@ describe('testing addType for schema definition mode `code`', () => { describe('testing all GraphQL Types', () => { test('scalar type id', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.id, }, @@ -55,7 +55,7 @@ describe('testing all GraphQL Types', () => { test('scalar type string', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.string, }, @@ -70,7 +70,7 @@ describe('testing all GraphQL Types', () => { test('scalar type int', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.int, }, @@ -85,7 +85,7 @@ describe('testing all GraphQL Types', () => { test('scalar type float', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.float, }, @@ -100,7 +100,7 @@ describe('testing all GraphQL Types', () => { test('scalar type boolean', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.boolean, }, @@ -115,7 +115,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSDate', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsDate, }, @@ -130,7 +130,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSTime', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsTime, }, @@ -145,7 +145,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSDateTime', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsDateTime, }, @@ -160,7 +160,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSTimestamp', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsTimestamp, }, @@ -175,7 +175,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSEmail', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsEmail, }, @@ -190,7 +190,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSJSON', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsJson, }, @@ -206,7 +206,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSUrl', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsUrl, }, @@ -221,7 +221,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSPhone', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsPhone, }, @@ -236,7 +236,7 @@ describe('testing all GraphQL Types', () => { test('scalar type AWSIPAddress', () => { // WHEN - api.addType('Test', { + api.addObjectType('Test', { definition: { id: t.awsIpAddress, }, 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 818f9ebfe9647..4cc633529ec28 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -62,7 +62,7 @@ describe('testing schema definition mode `file`', () => { }); }); - test('definition mode `file` errors when addType is called', () => { + test('definition mode `file` errors when addObjectType is called', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', @@ -71,10 +71,25 @@ describe('testing schema definition mode `file`', () => { // THEN expect(() => { - api.addType('blah', { + api.addObjectType('blah', { definition: { fail: t.id }, }); - }).toThrowError('API cannot add type because schema definition mode is not configured as CODE.'); + }).toThrowError('API cannot add object type because schema definition mode is not configured as CODE.'); + }); + + test('definition mode `file` errors when addInterfaceType is called', () => { + // WHEN + const api = new appsync.GraphQLApi(stack, 'API', { + name: 'demo', + schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), + }); + + // THEN + expect(() => { + api.addInterfaceType('blah', { + definition: { fail: t.id }, + }); + }).toThrowError('API cannot add interface type because schema definition mode is not configured as CODE.'); }); test('definition mode `file` errors when appendToSchema is called', () => { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json index 4527fed9237fd..0a6fc134d5511 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.expected.json @@ -16,7 +16,7 @@ "ApiId" ] }, - "Definition": "type Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\ntype Species {\n name: String\n classification: String\n designation: String\n averageHeight: Float\n averageLifespan: Int\n eyeColors: [String]\n hairColors: [String]\n skinColors: [String]\n language: String\n homeworld: Planet\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" + "Definition": "interface Node {\n created: String\n edited: String\n id: ID!\n}\ntype Planet {\n name: String\n diameter: Int\n rotationPeriod: Int\n orbitalPeriod: Int\n gravity: String\n population: [String]\n climates: [String]\n terrains: [String]\n surfaceWater: Float\n created: String\n edited: String\n id: ID!\n}\ntype Species implements Node {\n name: String\n classification: String\n designation: String\n averageHeight: Float\n averageLifespan: Int\n eyeColors: [String]\n hairColors: [String]\n skinColors: [String]\n language: String\n homeworld: Planet\n created: String\n edited: String\n id: ID!\n}\n\ntype Query {\n getPlanets: [Planet]\n}\n" } }, "codefirstapiDefaultApiKey89863A80": { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index bde6587a7765a..0cda2438be9e5 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -19,15 +19,26 @@ import * as ScalarType from './scalar-type-defintions'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'code-first-schema'); +const schema = appsync.Schema.fromCode(); + +const node = schema.addInterfaceType('Node', { + definition: { + created: ScalarType.string, + edited: ScalarType.string, + id: ScalarType.required_id, + }, +}); + const api = new appsync.GraphQLApi(stack, 'code-first-api', { name: 'api', - schema: appsync.Schema.fromCode(), + schema: schema, }); const planet = ObjectType.planet; -api.appendToSchema(planet.toString()); +schema.appendToSchema(planet.toString()); -api.addType('Species', { +api.addObjectType('Species', { + interfaceTypes: [node], definition: { name: ScalarType.string, classification: ScalarType.string, @@ -39,9 +50,6 @@ api.addType('Species', { skinColors: ScalarType.list_string, language: ScalarType.string, homeworld: planet.attribute(), - created: ScalarType.string, - edited: ScalarType.string, - id: ScalarType.required_id, }, }); From abd1057bee369c87168e4dc7d1d7889593ce9384 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 12:03:19 -0700 Subject: [PATCH 64/78] fix readme --- packages/@aws-cdk/aws-appsync/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index ea98de8d32c4b..d1f78b163d71c 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -289,7 +289,8 @@ const filmConnections = schema.generateEdgeAndConnection(schema.Film); api.addType('Query', { definition: { - allFilms: new appsync.ResolvableField(filmConnections.connection.attribute(), dummyDataSource, { + allFilms: new appsync.ResolvableField(dummyDataSource, { + returnType: filmConnections.connection.attribute(), args: schema.args, requestMappingTemplate: dummyRequest, responseMappingTemplate: dummyResponse, From c8408e0ebe516f19091a86633e0661db4a1dc524 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 13:25:59 -0700 Subject: [PATCH 65/78] add tests --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 12 + packages/@aws-cdk/aws-appsync/lib/schema.ts | 30 +- .../test/appsync-code-first.test.ts | 282 +++++++++++++++++- 3 files changed, 314 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 558c8154889ad..afdeb89338e4f 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -5,6 +5,7 @@ import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated' import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; import { Schema } from './schema'; import { InterfaceType, IntermediateTypeProps, ObjectType, ObjectTypeProps } from './schema-intermediate'; +import { IIntermediateType } from './schema-base'; /** * enum with all possible values for AppSync authorization type @@ -577,6 +578,17 @@ export class GraphQLApi extends GraphqlApiBase { this.schema.appendToSchema(addition, delimiter); } + /** + * Add type to the schema + * + * @param type the intermediate type to add to the schema + * + * @experimental + */ + public addType(type: IIntermediateType): Schema { + return this.schema.addType(type); + } + /** * Add an object type to the schema * diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index e3bd0b54b0c2b..a6d87a8006d94 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -2,6 +2,7 @@ import { readFileSync } from 'fs'; import { Lazy } from '@aws-cdk/core'; import { CfnGraphQLSchema } from './appsync.generated'; import { GraphQLApi } from './graphqlapi'; +import { IIntermediateType } from './schema-base'; import { InterfaceType, IntermediateTypeProps, ObjectTypeProps, ObjectType } from './schema-intermediate'; /** @@ -48,6 +49,15 @@ export abstract class Schema { */ public abstract appendToSchema(addition: string, delimiter?: string): void; + /** + * Add type to the schema + * + * @param type the intermediate type to add to the schema + * + * @experimental + */ + public abstract addType(type: IIntermediateType): Schema; + /** * Add an object type to the schema, if SchemaCode * @@ -104,6 +114,18 @@ export class SchemaCode extends Schema { this.definition = `${this.definition}${sep}${addition}\n`; } + /** + * Add type to the schema + * + * @param type the intermediate type to add to the schema + * + * @experimental + */ + public addType(type: IIntermediateType): Schema { + this.appendToSchema(Lazy.stringValue({ produce: () => type.toString() })); + return this; + } + /** * Add an object type to the schema * @@ -116,7 +138,7 @@ export class SchemaCode extends Schema { const type = new ObjectType(name, { ...props, }); - this.appendToSchema(Lazy.stringValue({ produce: () => type.toString() })); + this.addType(type); return type; } @@ -132,7 +154,7 @@ export class SchemaCode extends Schema { const type = new InterfaceType(name, { ...props, }); - this.appendToSchema(Lazy.stringValue({ produce: () => type.toString() })); + this.addType(type);; return type; } } @@ -160,7 +182,9 @@ export class SchemaFile extends Schema { public appendToSchema(_addition: string, _delimiter?: string): void { throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); } - + public addType(_type: IIntermediateType): Schema { + throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); + } public addObjectType(_name: string, _props: ObjectTypeProps): ObjectType { throw new Error('API cannot add object type because schema definition mode is not configured as CODE.'); } diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 49531e44a66d5..914f9b77dd66e 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -4,18 +4,64 @@ import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; let stack: cdk.Stack; -let api: appsync.GraphQLApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'api', { - name: 'api', - schema: appsync.Schema.fromCode(), - }); }); -describe('testing addObjectType for schema definition mode `code`', () => { - test('check scalar type id with all options', () => { +describe('code-first implementation through GraphQL Api functions`', () => { + let api: appsync.GraphQLApi; + beforeEach(() => { + // GIVEN + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schema: appsync.Schema.fromCode(), + }); + }); + + test('testing addType w/ Interface Type for schema definition mode `code`', () => { + // WHEN + const test = new appsync.InterfaceType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + api.addType(test); + test.addField('dupid', t.dup_id); + const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('testing addType w/ Object Type for schema definition mode `code`', () => { + // WHEN + const test = new appsync.ObjectType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + api.addType(test); + test.addField('dupid', t.dup_id); + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('testing addObjectType for schema definition mode `code`', () => { // WHEN api.addObjectType('Test', { definition: { @@ -34,4 +80,226 @@ describe('testing addObjectType for schema definition mode `code`', () => { Definition: `${out}`, }); }); + + test('addField dynamically adds field to schema', () => { + // WHEN + const test = api.addObjectType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + + test.addField('dupid', t.dup_id); + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('testing addInterfaceType for schema definition mode `code`', () => { + // WHEN + api.addInterfaceType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + dupid: t.dup_id, + }, + }); + const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('addField dynamically adds field to schema', () => { + // WHEN + const test = api.addInterfaceType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + + test.addField('dupid', t.dup_id); + const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); +}); + +describe('code-first implementation through Schema functions`', () => { + let schema: appsync.Schema; + beforeEach(() => { + // GIVEN + schema = appsync.Schema.fromCode(); + }); + + test('testing addType w/ Interface Type for schema definition mode `code`', () => { + // WHEN + const test = new appsync.InterfaceType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + schema.addType(test); + test.addField('dupid', t.dup_id); + + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schema, + }); + const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('testing addType w/ Object Type for schema definition mode `code`', () => { + // WHEN + const test = new appsync.ObjectType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + schema.addType(test); + test.addField('dupid', t.dup_id); + + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schema, + }); + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('testing addObjectType for schema definition mode `code`', () => { + // WHEN + schema.addObjectType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + dupid: t.dup_id, + }, + }); + + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schema, + }); + + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('addField dynamically adds field to schema', () => { + // WHEN + const test = schema.addObjectType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + + test.addField('dupid', t.dup_id); + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schema, + }); + const out = 'type Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('testing addInterfaceType for schema definition mode `code`', () => { + // WHEN + schema.addInterfaceType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + dupid: t.dup_id, + }, + }); + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schema, + }); + const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('addField dynamically adds field to schema', () => { + // WHEN + const test = schema.addInterfaceType('Test', { + definition: { + id: t.id, + lid: t.list_id, + rid: t.required_id, + rlid: t.required_list_id, + rlrid: t.required_list_required_id, + }, + }); + + test.addField('dupid', t.dup_id); + new appsync.GraphQLApi(stack, 'api', { + name: 'api', + schema, + }); + const out = 'interface Test {\n id: ID\n lid: [ID]\n rid: ID!\n rlid: [ID]!\n rlrid: [ID!]!\n dupid: [ID!]!\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); }); From 0fd0f2caaf8a321569680cee8a9c59e033005438 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 14:14:38 -0700 Subject: [PATCH 66/78] allow public definition --- packages/@aws-cdk/aws-appsync/lib/schema.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index a6d87a8006d94..c065081c90f90 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -29,6 +29,11 @@ export abstract class Schema { protected schema?: CfnGraphQLSchema; + /** + * The definition for this schema + */ + public abstract definition: string; + /** * Called when the GraphQL Api is initialized to allow this object to bind * to the stack. @@ -83,7 +88,7 @@ export abstract class Schema { * GraphQL Schema that is mutable through code-first approach */ export class SchemaCode extends Schema { - private definition: string; + public definition: string; constructor() { super(); this.definition = ''; @@ -163,10 +168,14 @@ export class SchemaCode extends Schema { * GraphQL Schema that is declared through a schema-first approach */ export class SchemaFile extends Schema { + + public definition: string; private filePath: string; + constructor(filePath: string) { super(); this.filePath = filePath; + this.definition = readFileSync(this.filePath).toString('UTF-8'); } public bind(api: GraphQLApi): CfnGraphQLSchema { From 7ffe7c75ecb685b95e42fafbadaad39efda9b574 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 17:05:53 -0700 Subject: [PATCH 67/78] address changes --- packages/@aws-cdk/aws-appsync/README.md | 8 +- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 13 +- packages/@aws-cdk/aws-appsync/lib/private.ts | 8 + packages/@aws-cdk/aws-appsync/lib/schema.ts | 162 ++++++------------ .../aws-appsync/test/appsync-auth.test.ts | 46 ++--- .../test/appsync-code-first.test.ts | 3 +- .../aws-appsync/test/appsync-dynamodb.test.ts | 2 +- .../aws-appsync/test/appsync-grant.test.ts | 2 +- .../aws-appsync/test/appsync-http.test.ts | 2 +- .../test/appsync-interface-type.test.ts | 9 +- .../aws-appsync/test/appsync-lambda.test.ts | 2 +- .../aws-appsync/test/appsync-none.test.ts | 2 +- .../test/appsync-object-type.test.ts | 23 ++- .../test/appsync-scalar-type.test.ts | 1 - .../aws-appsync/test/appsync-schema.test.ts | 22 ++- .../@aws-cdk/aws-appsync/test/appsync.test.ts | 4 +- .../aws-appsync/test/integ.api-import.ts | 2 +- .../aws-appsync/test/integ.graphql-iam.ts | 2 +- .../aws-appsync/test/integ.graphql-schema.ts | 6 +- .../aws-appsync/test/integ.graphql.ts | 2 +- 20 files changed, 135 insertions(+), 186 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index e0b12a26ae1d2..0b1cf5396f98d 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -309,8 +309,8 @@ api.addType('Query', { }); }) -this.objectTypes.map((t) => api.appendToSchema(t)); -Object.keys(filmConnections).forEach((key) => api.appendToSchema(filmConnections[key])); +this.objectTypes.map((t) => api.addToSchema(t)); +Object.keys(filmConnections).forEach((key) => api.addToSchema(filmConnections[key])); ``` Notice how we can utilize the `generateEdgeAndConnection` function to generate @@ -476,7 +476,7 @@ You can create Object Types in three ways: }, }); - api.appendToSchema(object.toString()); + api.addToSchema(object.toString()); ``` > This method allows for reusability and modularity, ideal for larger projects. For example, imagine moving all Object Type definition outside the stack. @@ -500,7 +500,7 @@ You can create Object Types in three ways: `cdk-stack.ts` - a file containing our cdk stack ```ts import { demo } from './object-types'; - api.appendToSchema(demo.toString()); + api.addToSchema(demo.toString()); ``` 2. Object Types can be created ***externally*** from an Interface Type. diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index afdeb89338e4f..b83ad504cbc9a 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -4,8 +4,8 @@ import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/c import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; import { Schema } from './schema'; -import { InterfaceType, IntermediateTypeProps, ObjectType, ObjectTypeProps } from './schema-intermediate'; import { IIntermediateType } from './schema-base'; +import { InterfaceType, IntermediateTypeProps, ObjectType, ObjectTypeProps } from './schema-intermediate'; /** * enum with all possible values for AppSync authorization type @@ -228,12 +228,13 @@ export interface GraphQLApiProps { /** * GraphQL schema definition. Specify how you want to define your schema. * - * Schema.fromCode allows schema definition through CDK * Schema.fromFile(filePath: string) allows schema definition through schema.graphql file * + * @default - schema will be generated code-first (i.e. addType, addObjectType, etc.) + * * @experimental */ - readonly schema: Schema; + readonly schema?: Schema; /** * A flag indicating whether or not X-Ray tracing is enabled for the GraphQL API. * @@ -409,7 +410,7 @@ export class GraphQLApi extends GraphqlApiBase { this.arn = this.api.attrArn; this.graphQlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; - this.schema = props.schema; + this.schema = props.schema ?? new Schema(); this._schema = this.schema.bind(this); if (modes.some((mode) => mode.authorizationType === AuthorizationType.API_KEY)) { @@ -574,8 +575,8 @@ export class GraphQLApi extends GraphqlApiBase { * * @experimental */ - public appendToSchema(addition: string, delimiter?: string): void { - this.schema.appendToSchema(addition, delimiter); + public addToSchema(addition: string, delimiter?: string): void { + this.schema.addToSchema(addition, delimiter); } /** diff --git a/packages/@aws-cdk/aws-appsync/lib/private.ts b/packages/@aws-cdk/aws-appsync/lib/private.ts index 9118b503349c3..696a0f0e7d5f6 100644 --- a/packages/@aws-cdk/aws-appsync/lib/private.ts +++ b/packages/@aws-cdk/aws-appsync/lib/private.ts @@ -4,6 +4,14 @@ function concatAndDedup(left: T[], right: T[]): T[] { }); } +/** + * Utility enum for Schema class + */ +export enum SchemaMode { + FILE = 'FILE', + CODE = 'CODE', +}; + /** * Utility class to represent DynamoDB key conditions. */ diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index c065081c90f90..dcfbd448c9a52 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -2,98 +2,69 @@ import { readFileSync } from 'fs'; import { Lazy } from '@aws-cdk/core'; import { CfnGraphQLSchema } from './appsync.generated'; import { GraphQLApi } from './graphqlapi'; +import { SchemaMode } from './private'; import { IIntermediateType } from './schema-base'; import { InterfaceType, IntermediateTypeProps, ObjectTypeProps, ObjectType } from './schema-intermediate'; /** - * The Schema for a GraphQL Api + * The options for configuring a schema + * + * If no options are specified, then the schema will + * be generated code-first. */ -export abstract class Schema { +export interface SchemaOptions { /** - * Generate a Schema code-first + * The file path for the schema. When this option is + * configured, then the schema will be generated from an + * existing file from disk. * - * @returns `SchemaCode` with mutable schema defintion + * @default - schema not configured through disk asset */ - public static fromCode(): SchemaCode { - return new SchemaCode(); - } + readonly filePath?: string, +}; + +/** + * The Schema for a GraphQL Api + * + * If no options are configured, schema will be generated + * code-first. + */ +export class Schema { /** * Generate a Schema from file * - * @returns `SchemaFile` with immutable schema defintion + * @returns `SchemaAsset` with immutable schema defintion * @param filePath the file path of the schema file */ - public static fromFile(filePath: string): SchemaFile { - return new SchemaFile(filePath); + public static fromAsset(filePath: string): Schema { + return new Schema({ filePath }); } - protected schema?: CfnGraphQLSchema; - /** * The definition for this schema */ - public abstract definition: string; - - /** - * Called when the GraphQL Api is initialized to allow this object to bind - * to the stack. - * - * @param api The binding GraphQL Api - */ - public abstract bind(api: GraphQLApi): CfnGraphQLSchema; + public definition: string; - /** - * Escape hatch to append to Schema as desired. Will always result - * in a newline. - * - * @param addition the addition to add to schema - * @param delimiter the delimiter between schema and addition - * @default - '' - * - * @experimental - */ - public abstract appendToSchema(addition: string, delimiter?: string): void; + protected schema?: CfnGraphQLSchema; - /** - * Add type to the schema - * - * @param type the intermediate type to add to the schema - * - * @experimental - */ - public abstract addType(type: IIntermediateType): Schema; + private mode: SchemaMode; - /** - * Add an object type to the schema, if SchemaCode - * - * @param name the name of the object type - * @param props the definition - * - * @experimental - */ - public abstract addObjectType(name: string, props: ObjectTypeProps): ObjectType; + public constructor(options?: SchemaOptions) { + if (options?.filePath) { + this.mode = SchemaMode.FILE; + this.definition = readFileSync(options.filePath).toString('UTF-8'); + } else { + this.mode = SchemaMode.CODE; + this.definition = ''; + } + } /** - * Add an interface type to the schema - * - * @param name the name of the interface type - * @param props the definition + * Called when the GraphQL Api is initialized to allow this object to bind + * to the stack. * - * @experimental + * @param api The binding GraphQL Api */ - public abstract addInterfaceType(name: string, props: IntermediateTypeProps): InterfaceType; -} - -/** - * GraphQL Schema that is mutable through code-first approach - */ -export class SchemaCode extends Schema { - public definition: string; - constructor() { - super(); - this.definition = ''; - } - public bind(api: GraphQLApi): CfnGraphQLSchema { if (!this.schema) { this.schema = new CfnGraphQLSchema(api, 'Schema', { @@ -104,8 +75,9 @@ export class SchemaCode extends Schema { return this.schema; } + /** - * Escape hatch to append to Schema as desired. Will always result + * Escape hatch to add to Schema as desired. Will always result * in a newline. * * @param addition the addition to add to schema @@ -114,7 +86,10 @@ export class SchemaCode extends Schema { * * @experimental */ - public appendToSchema(addition: string, delimiter?: string): void { + public addToSchema(addition: string, delimiter?: string): void { + if (this.mode !== SchemaMode.CODE) { + throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); + } const sep = delimiter ?? ''; this.definition = `${this.definition}${sep}${addition}\n`; } @@ -127,7 +102,10 @@ export class SchemaCode extends Schema { * @experimental */ public addType(type: IIntermediateType): Schema { - this.appendToSchema(Lazy.stringValue({ produce: () => type.toString() })); + if (this.mode !== SchemaMode.CODE) { + throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); + } + this.addToSchema(Lazy.stringValue({ produce: () => type.toString() })); return this; } @@ -140,6 +118,9 @@ export class SchemaCode extends Schema { * @experimental */ public addObjectType(name: string, props: ObjectTypeProps): ObjectType { + if (this.mode !== SchemaMode.CODE) { + throw new Error('API cannot add object type because schema definition mode is not configured as CODE.'); + } const type = new ObjectType(name, { ...props, }); @@ -156,48 +137,13 @@ export class SchemaCode extends Schema { * @experimental */ public addInterfaceType(name: string, props: IntermediateTypeProps): InterfaceType { + if (this.mode != SchemaMode.CODE) { + throw new Error('API cannot add interface type because schema definition mode is not configured as CODE.'); + } const type = new InterfaceType(name, { ...props, }); this.addType(type);; return type; } -} - -/** - * GraphQL Schema that is declared through a schema-first approach - */ -export class SchemaFile extends Schema { - - public definition: string; - private filePath: string; - - constructor(filePath: string) { - super(); - this.filePath = filePath; - this.definition = readFileSync(this.filePath).toString('UTF-8'); - } - - public bind(api: GraphQLApi): CfnGraphQLSchema { - if (!this.schema) { - this.schema = new CfnGraphQLSchema(api, 'Schema', { - apiId: api.apiId, - definition: readFileSync(this.filePath).toString('UTF-8'), - }); - } - return this.schema; - } - - public appendToSchema(_addition: string, _delimiter?: string): void { - throw new Error('API cannot append to schema because schema definition mode is not configured as CODE.'); - } - public addType(_type: IIntermediateType): Schema { - throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); - } - public addObjectType(_name: string, _props: ObjectTypeProps): ObjectType { - throw new Error('API cannot add object type because schema definition mode is not configured as CODE.'); - } - public addInterfaceType(_name: string, _props: IntermediateTypeProps): InterfaceType { - throw new Error('API cannot add interface type because schema definition mode is not configured as CODE.'); - } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts index 6bdf87f03901f..e9ad68e4e66e0 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts @@ -15,7 +15,7 @@ describe('AppSync API Key Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); // THEN @@ -26,7 +26,7 @@ describe('AppSync API Key Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, additionalAuthorizationModes: [ @@ -43,7 +43,7 @@ describe('AppSync API Key Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, }, @@ -57,7 +57,7 @@ describe('AppSync API Key Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, additionalAuthorizationModes: [], @@ -72,7 +72,7 @@ describe('AppSync API Key Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, additionalAuthorizationModes: [{ @@ -92,7 +92,7 @@ describe('AppSync API Key Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, additionalAuthorizationModes: [ @@ -119,7 +119,7 @@ describe('AppSync API Key Authorization', () => { expect(() => { new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { additionalAuthorizationModes: [{ authorizationType: appsync.AuthorizationType.API_KEY, @@ -134,7 +134,7 @@ describe('AppSync API Key Authorization', () => { expect(() => { new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.API_KEY }, additionalAuthorizationModes: [{ @@ -150,7 +150,7 @@ describe('AppSync API Key Authorization', () => { expect(() => { new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, additionalAuthorizationModes: [ @@ -168,7 +168,7 @@ describe('AppSync IAM Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, }, @@ -184,7 +184,7 @@ describe('AppSync IAM Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { additionalAuthorizationModes: [{ authorizationType: appsync.AuthorizationType.IAM }], }, @@ -201,7 +201,7 @@ describe('AppSync IAM Authorization', () => { expect(() => { new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM }, additionalAuthorizationModes: [{ authorizationType: appsync.AuthorizationType.IAM }], @@ -215,7 +215,7 @@ describe('AppSync IAM Authorization', () => { expect(() => { new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { additionalAuthorizationModes: [ { authorizationType: appsync.AuthorizationType.IAM }, @@ -236,7 +236,7 @@ describe('AppSync User Pool Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.USER_POOL, @@ -259,7 +259,7 @@ describe('AppSync User Pool Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.USER_POOL, @@ -288,7 +288,7 @@ describe('AppSync User Pool Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { additionalAuthorizationModes: [{ authorizationType: appsync.AuthorizationType.USER_POOL, @@ -313,7 +313,7 @@ describe('AppSync User Pool Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { additionalAuthorizationModes: [{ authorizationType: appsync.AuthorizationType.USER_POOL, @@ -343,7 +343,7 @@ describe('AppSync User Pool Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.USER_POOL, @@ -399,7 +399,7 @@ describe('AppSync OIDC Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.OIDC, @@ -421,7 +421,7 @@ describe('AppSync OIDC Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.OIDC, @@ -451,7 +451,7 @@ describe('AppSync OIDC Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { additionalAuthorizationModes: [{ authorizationType: appsync.AuthorizationType.OIDC, @@ -475,7 +475,7 @@ describe('AppSync OIDC Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { additionalAuthorizationModes: [{ authorizationType: appsync.AuthorizationType.OIDC, @@ -507,7 +507,7 @@ describe('AppSync OIDC Authorization', () => { // WHEN new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.OIDC, diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 914f9b77dd66e..d866e71598f5e 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -15,7 +15,6 @@ describe('code-first implementation through GraphQL Api functions`', () => { // GIVEN api = new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromCode(), }); }); @@ -148,7 +147,7 @@ describe('code-first implementation through Schema functions`', () => { let schema: appsync.Schema; beforeEach(() => { // GIVEN - schema = appsync.Schema.fromCode(); + schema = new appsync.Schema(); }); test('testing addType w/ Interface Type for schema definition mode `code`', () => { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts index 984fc91be71a5..e54a9576396d1 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts @@ -15,7 +15,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); }); 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 fefbc3b55a5cc..44251cd7fabee 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts @@ -15,7 +15,7 @@ beforeEach(() => { }); api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM, diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts index 5fed59f164f81..899a76ebd19f4 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts @@ -11,7 +11,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); endpoint = 'aws.amazon.com'; }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts index 81bc6a0915132..981ec8b60a039 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts @@ -10,7 +10,6 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromCode(), }); }); @@ -25,7 +24,7 @@ describe('testing InterfaceType properties', () => { }); test('basic InterfaceType produces correct schema', () => { // WHEN - api.appendToSchema(baseTest.toString()); + api.addToSchema(baseTest.toString()); const out = 'interface baseTest {\n id: ID\n}\n'; // THEN @@ -40,7 +39,7 @@ describe('testing InterfaceType properties', () => { returnType: t.string, args: { success: t.int }, })); - api.appendToSchema(baseTest.toString()); + api.addToSchema(baseTest.toString()); const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; // THEN @@ -56,7 +55,7 @@ describe('testing InterfaceType properties', () => { args: { success: t.int }, dataSource: api.addNoneDataSource('none'), })); - api.appendToSchema(baseTest.toString()); + api.addToSchema(baseTest.toString()); const out = 'interface baseTest {\n id: ID\n test(success: Int): String\n}\n'; // THEN @@ -75,7 +74,7 @@ describe('testing InterfaceType properties', () => { test: graphqlType, }, }); - api.appendToSchema(test.toString()); + api.addToSchema(test.toString()); const out = 'type Test {\n test: baseTest\n}\n'; // THEN diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts index a8536b0cb64af..a67fd4b1691b7 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts @@ -11,7 +11,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts index 33f35a51fc908..f2b52c7dfba03 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts @@ -10,7 +10,7 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts index d5fd8d3b81767..a60a5242f6fe7 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts @@ -10,7 +10,6 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromCode(), }); }); @@ -30,8 +29,8 @@ describe('testing Object Type properties', () => { directives: [appsync.Directive.custom('@test')], }); - api.appendToSchema(baseTest.toString()); - api.appendToSchema(objectTest.toString()); + api.addToSchema(baseTest.toString()); + api.addToSchema(objectTest.toString()); const gql_interface = 'interface baseTest {\n id: ID\n}\n'; const gql_object = 'type objectTest implements baseTest @test {\n id2: ID\n id: ID\n}\n'; const out = `${gql_interface}${gql_object}`; @@ -57,9 +56,9 @@ describe('testing Object Type properties', () => { }, }); - api.appendToSchema(baseTest.toString()); - api.appendToSchema(anotherTest.toString()); - api.appendToSchema(objectTest.toString()); + api.addToSchema(baseTest.toString()); + api.addToSchema(anotherTest.toString()); + api.addToSchema(objectTest.toString()); const gql_interface = 'interface baseTest {\n id: ID\n}\ninterface anotherTest {\n id2: ID\n}\n'; const gql_object = 'type objectTest implements anotherTest, baseTest {\n id3: ID\n id2: ID\n id: ID\n}\n'; @@ -84,7 +83,7 @@ describe('testing Object Type properties', () => { test: graphqlType, }, }); - api.appendToSchema(test.toString()); + api.addToSchema(test.toString()); const out = 'type Test {\n test: baseTest\n}\n'; // THEN @@ -108,7 +107,7 @@ describe('testing Object Type properties', () => { resolve: field, }, }); - api.appendToSchema(test.toString()); + api.addToSchema(test.toString()); const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; // THEN @@ -132,7 +131,7 @@ describe('testing Object Type properties', () => { resolve: field, }, }); - api.appendToSchema(test.toString()); + api.addToSchema(test.toString()); const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; // THEN @@ -155,7 +154,7 @@ describe('testing Object Type properties', () => { }), }, }); - api.appendToSchema(test.toString()); + api.addToSchema(test.toString()); // THEN expect(stack).toHaveResourceLike('AWS::AppSync::Resolver', { @@ -182,7 +181,7 @@ describe('testing Object Type properties', () => { // test.addField('resolve', field); test.addField('dynamic', t.string); - api.appendToSchema(test.toString()); + api.addToSchema(test.toString()); const out = 'type Test {\n test: String\n resolve(arg: Int): String\n dynamic: String\n}\n'; // THEN @@ -215,7 +214,7 @@ describe('testing Object Type properties', () => { // test.addField('resolve', field); test.addField('dynamic', garbage.attribute()); - api.appendToSchema(test.toString()); + api.addToSchema(test.toString()); const out = 'type Test {\n test: String\n resolve(arg: Garbage): Garbage\n dynamic: Garbage\n}\n'; // THEN diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts index 7fc66d2785eb7..43364c0e50705 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts @@ -10,7 +10,6 @@ beforeEach(() => { stack = new cdk.Stack(); api = new appsync.GraphQLApi(stack, 'api', { name: 'api', - schema: appsync.Schema.fromCode(), }); }); 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 4cc633529ec28..948e4e77732b8 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -21,7 +21,6 @@ describe('basic testing schema definition mode `code`', () => { // WHEN new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schema: appsync.Schema.fromCode(), }); // THEN @@ -30,15 +29,14 @@ describe('basic testing schema definition mode `code`', () => { }); }); - test('definition mode `code` generates correct schema with appendToSchema', () => { + test('definition mode `code` generates correct schema with addToSchema', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schema: appsync.Schema.fromCode(), }); - api.appendToSchema(type); - api.appendToSchema(query); - api.appendToSchema(mutation); + api.addToSchema(type); + api.addToSchema(query); + api.addToSchema(mutation); // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { @@ -53,7 +51,7 @@ describe('testing schema definition mode `file`', () => { // WHEN new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); // THEN @@ -66,7 +64,7 @@ describe('testing schema definition mode `file`', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); // THEN @@ -81,7 +79,7 @@ describe('testing schema definition mode `file`', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); // THEN @@ -92,16 +90,16 @@ describe('testing schema definition mode `file`', () => { }).toThrowError('API cannot add interface type because schema definition mode is not configured as CODE.'); }); - test('definition mode `file` errors when appendToSchema is called', () => { + test('definition mode `file` errors when addToSchema is called', () => { // WHEN const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', - schema: appsync.Schema.fromFile(join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); // THEN expect(() => { - api.appendToSchema('blah'); + api.addToSchema('blah'); }).toThrowError('API cannot append to schema because schema definition mode is not configured as CODE.'); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index f69da04e99939..9dc6808a672e1 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -10,7 +10,7 @@ beforeEach(() => { api = new appsync.GraphQLApi(stack, 'api', { authorizationConfig: {}, name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); }); @@ -64,7 +64,7 @@ test('when xray is enabled should not throw an Error', () => { new appsync.GraphQLApi(stack, 'api-x-ray', { authorizationConfig: {}, name: 'api', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), xrayEnabled: true, }); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts index c403e395a563b..b36c6a1bef7b1 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts @@ -24,7 +24,7 @@ const baseStack = new cdk.Stack(app, 'baseStack'); const baseApi = new appsync.GraphQLApi(baseStack, 'baseApi', { name: 'baseApi', - schema: appsync.Schema.fromFile(path.join(__dirname, 'appsync.test.graphql')), + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); const stack = new cdk.Stack(app, 'stack'); 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 270b12fbf13b4..0d4c95a10ea3f 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts @@ -38,7 +38,7 @@ const userPool = new UserPool(stack, 'Pool', { const api = new GraphQLApi(stack, 'Api', { name: 'Integ_Test_IAM', - schema: Schema.fromFile(join(__dirname, 'integ.graphql-iam.graphql')), + schema: Schema.fromAsset(join(__dirname, 'integ.graphql-iam.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: AuthorizationType.USER_POOL, diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 0cda2438be9e5..727167ee277cf 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -19,7 +19,7 @@ import * as ScalarType from './scalar-type-defintions'; const app = new cdk.App(); const stack = new cdk.Stack(app, 'code-first-schema'); -const schema = appsync.Schema.fromCode(); +const schema = new appsync.Schema(); const node = schema.addInterfaceType('Node', { definition: { @@ -35,7 +35,7 @@ const api = new appsync.GraphQLApi(stack, 'code-first-api', { }); const planet = ObjectType.planet; -schema.appendToSchema(planet.toString()); +schema.addToSchema(planet.toString()); api.addObjectType('Species', { interfaceTypes: [node], @@ -53,7 +53,7 @@ api.addObjectType('Species', { }, }); -api.appendToSchema('type Query {\n getPlanets: [Planet]\n}', '\n'); +api.addToSchema('type Query {\n getPlanets: [Planet]\n}', '\n'); const table = new db.Table(stack, 'table', { partitionKey: { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 458fa91813acd..715864b209bc1 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -36,7 +36,7 @@ const userPool = new UserPool(stack, 'Pool', { const api = new GraphQLApi(stack, 'Api', { name: 'demoapi', - schema: Schema.fromFile(join(__dirname, 'integ.graphql.graphql')), + schema: Schema.fromAsset(join(__dirname, 'integ.graphql.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: AuthorizationType.USER_POOL, From 70d584ef4cf76ebdaea12f227cc3d55557c58252 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 24 Aug 2020 17:26:44 -0700 Subject: [PATCH 68/78] update readme --- packages/@aws-cdk/aws-appsync/README.md | 63 ++++++++++++++++++------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 0b1cf5396f98d..72242d2351931 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -85,13 +85,48 @@ demoDS.createResolver({ ### Schema Every GraphQL Api needs a schema to define the Api. CDK offers `appsync.Schema` -for static convenience methods for various types of schema declaration. +for static convenience methods for various types of schema declaration: code-first +or schema-first. -- `appsync.Schema.fromFile(filePath)` - specify a file in the local - filesystem which will be read as the schema -- `appsync.Schema.fromCode()` - configure schema declaration through a code-first - approach. See the [code-first schema](#Code-First-Schema) section for more - details. +#### Code-First + +When declaring your GraphQL Api, CDK defaults to a code-first approach if the +`schema` property is not configured. + +```ts +const api = appsync.GraphQLApi(stack, 'api', { name: 'myApi' }); +``` + +CDK will declare a `Schema` class that will give your Api access functions to +define your schema code-first: `addType`, `addObjectType`, `addToSchema`, etc. + +You can also declare your `Schema` class outside of your CDK stack, to define +your schema externally. + +```ts +const schema = new appsync.Schema(); +schema.addObjectType('demo', { + definition: { id: appsync.GraphqlType.id() }, +}); +const api = appsync.GraphQLApi(stack, 'api', { + name: 'myApi', + schema +}); +``` + +See the [code-first schema](#Code-First-Schema) section for more details. + +#### Schema-First + +You can define your GraphQL Schema from a file on disk. For convenience, use +the `appsync.Schema.fromAsset` to specify the file representing your schema. + +```ts +const api = appsync.GraphQLApi(stack, 'api', { + name: 'myApi', + schema: appsync.Schema.fromAsset(join(__dirname, 'schema.graphl')), +}); +``` ### Imports @@ -290,7 +325,6 @@ import * as schema from './object-types'; const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schema: appsync.Schema.fromCode(), }); this.objectTypes = [ schema.Node, schema.Film ]; @@ -304,13 +338,12 @@ api.addType('Query', { args: schema.args, requestMappingTemplate: dummyRequest, responseMappingTemplate: dummyResponse, - }, + }), } - }); -}) +}); -this.objectTypes.map((t) => api.addToSchema(t)); -Object.keys(filmConnections).forEach((key) => api.addToSchema(filmConnections[key])); +this.objectTypes.map((t) => api.addType(t)); +Object.keys(filmConnections).forEach((key) => api.addType(filmConnections[key])); ``` Notice how we can utilize the `generateEdgeAndConnection` function to generate @@ -467,7 +500,6 @@ You can create Object Types in three ways: ```ts const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schema: appsync.Schema.fromCode(), }); const demo = new appsync.ObjectType('Demo', { defintion: { @@ -476,7 +508,7 @@ You can create Object Types in three ways: }, }); - api.addToSchema(object.toString()); + api.addType(object); ``` > This method allows for reusability and modularity, ideal for larger projects. For example, imagine moving all Object Type definition outside the stack. @@ -500,7 +532,7 @@ You can create Object Types in three ways: `cdk-stack.ts` - a file containing our cdk stack ```ts import { demo } from './object-types'; - api.addToSchema(demo.toString()); + api.addType(demo); ``` 2. Object Types can be created ***externally*** from an Interface Type. @@ -523,7 +555,6 @@ You can create Object Types in three ways: ```ts const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schema: appsync.Schema.fromCode(), }); api.addType('Demo', { defintion: { From 8bb0a2aa8043603c6a09ee34aa023a1521a0f54f Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 25 Aug 2020 08:55:17 -0700 Subject: [PATCH 69/78] address suggestions --- packages/@aws-cdk/aws-appsync/README.md | 6 +++--- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 72242d2351931..88f059fc30b95 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -47,7 +47,7 @@ import * as db from '@aws-cdk/aws-dynamodb'; const api = new appsync.GraphQLApi(stack, 'Api', { name: 'demo', - schema: appsync.Schema.fromFile(join(__dirname, 'schema.graphql')), + schema: appsync.Schema.fromAsset(join(__dirname, 'schema.graphql')), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.IAM @@ -94,7 +94,7 @@ When declaring your GraphQL Api, CDK defaults to a code-first approach if the `schema` property is not configured. ```ts -const api = appsync.GraphQLApi(stack, 'api', { name: 'myApi' }); +const api = new appsync.GraphQLApi(stack, 'api', { name: 'myApi' }); ``` CDK will declare a `Schema` class that will give your Api access functions to @@ -108,7 +108,7 @@ const schema = new appsync.Schema(); schema.addObjectType('demo', { definition: { id: appsync.GraphqlType.id() }, }); -const api = appsync.GraphQLApi(stack, 'api', { +const api = new appsync.GraphQLApi(stack, 'api', { name: 'myApi', schema }); diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index b83ad504cbc9a..125d96f441d64 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -382,9 +382,9 @@ export class GraphQLApi extends GraphqlApiBase { */ public readonly apiKey?: string; - private _schema: CfnGraphQLSchema; + private schemaResource: CfnGraphQLSchema; private api: CfnGraphQLApi; - private _apiKey?: CfnApiKey; + private apiKeyResource?: CfnApiKey; constructor(scope: Construct, id: string, props: GraphQLApiProps) { super(scope, id); @@ -411,15 +411,15 @@ export class GraphQLApi extends GraphqlApiBase { this.graphQlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; this.schema = props.schema ?? new Schema(); - this._schema = this.schema.bind(this); + this.schemaResource = this.schema.bind(this); if (modes.some((mode) => mode.authorizationType === AuthorizationType.API_KEY)) { const config = modes.find((mode: AuthorizationMode) => { return mode.authorizationType === AuthorizationType.API_KEY && mode.apiKeyConfig; })?.apiKeyConfig; - this._apiKey = this.createAPIKey(config); - this._apiKey.addDependsOn(this._schema); - this.apiKey = this._apiKey.attrApiKey; + this.apiKeyResource = this.createAPIKey(config); + this.apiKeyResource.addDependsOn(this.schemaResource); + this.apiKey = this.apiKeyResource.attrApiKey; } } @@ -496,7 +496,7 @@ export class GraphQLApi extends GraphqlApiBase { * @param construct the dependee */ public addSchemaDependency(construct: CfnResource): boolean { - construct.addDependsOn(this._schema); + construct.addDependsOn(this.schemaResource); return true; } From b3f6843327ec63f73d7a4fc4d967e372183d7deb Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 25 Aug 2020 09:09:10 -0700 Subject: [PATCH 70/78] jsii bug --- packages/@aws-cdk/aws-appsync/lib/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index dcfbd448c9a52..52371fc6c8799 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -52,7 +52,7 @@ export class Schema { public constructor(options?: SchemaOptions) { if (options?.filePath) { this.mode = SchemaMode.FILE; - this.definition = readFileSync(options.filePath).toString('UTF-8'); + this.definition = readFileSync(options.filePath).toString('utf-8'); } else { this.mode = SchemaMode.CODE; this.definition = ''; From bace3452a208084956a2b46fb43aba577cfcc0d5 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 25 Aug 2020 15:20:45 -0700 Subject: [PATCH 71/78] create baseline for tests directives --- .../@aws-cdk/aws-appsync/lib/schema-base.ts | 37 ++++++++ .../@aws-cdk/aws-appsync/lib/schema-field.ts | 33 +++++-- .../aws-appsync/lib/schema-intermediate.ts | 94 +++++++++---------- .../test/appsync-directives.test.ts | 94 +++++++++++++++++++ .../test/appsync-interface-type.test.ts | 44 +++++++++ .../test/appsync-object-type.test.ts | 63 +++++++------ 6 files changed, 281 insertions(+), 84 deletions(-) create mode 100644 packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index c3f67fe46a595..47bdac7500d2d 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -61,6 +61,11 @@ export interface IField { * Generate the arguments for this field */ argsToString(): string; + + /** + * Generate the directives for this field + */ + directivesToString(): string } /** @@ -134,6 +139,38 @@ export class Directive { return new Directive('@aws_iam'); } + /** + * Add the @aws_oidc directive + */ + public static oidc(): Directive { + return new Directive('@aws_oidc'); + } + + /** + * Add the @aws_api_key directive + */ + public static apiKey(): Directive { + return new Directive('@aws_api_key'); + } + + /** + * Add the @aws_auth or @aws_cognito_user_pools directive + * + * @param groups the groups to allow access to + * @param additional is cognito an additional authorization? + * @default false + */ + public static cognito(groups: string[], additional?: boolean): Directive { + if (groups.length === 0) { + throw new Error('Groups parameter must not be empty.'); + } + const prefix = additional ? '@aws_cognito_user_pools' : '@aws_auth'; + const stringify = (array: string[]): string => { + return array.reduce((acc, element) => `${acc}"${element}", `, '[').slice(0, -2) + ']'; + }; + return new Directive(`${prefix}(cognito_groups: ${stringify(groups)})`); + } + /** * Add a custom directive * diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index ba22dd085fe56..41fdbd79dac22 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -1,6 +1,6 @@ import { BaseDataSource } from './data-source'; import { MappingTemplate } from './mapping-template'; -import { Type, IField, IIntermediateType } from './schema-base'; +import { Type, IField, IIntermediateType, Directive } from './schema-base'; /** * Base options for GraphQL Types @@ -321,6 +321,13 @@ export class GraphqlType implements IField { public argsToString(): string { return ''; } + + /** + * Generate the directives for this field + */ + public directivesToString(): string { + return ''; + } } /** @@ -345,6 +352,12 @@ export interface FieldOptions { * @default - no arguments */ readonly args?: { [key: string]: GraphqlType }; + /** + * the directives for this field + * + * @default - no directives + */ + readonly directives?: Directive[]; } /** @@ -375,13 +388,17 @@ export class Field extends GraphqlType implements IField { */ public argsToString(): string { if (!this.fieldOptions || !this.fieldOptions.args) { return ''; } - let args = '('; - Object.keys(this.fieldOptions?.args ?? {}).forEach((key) => { - const type = this.fieldOptions?.args?.[key].toString(); - args = `${args}${key}: ${type} `; - }); - args = args.slice(0, -1); - return `${args})`; + return Object.keys(this.fieldOptions.args).reduce((acc, key) => + `${acc}${key}: ${this.fieldOptions?.args?.[key].toString()} `, '(').slice(0, -1) + ')'; + } + + /** + * Generate the directives for this field + */ + public directivesToString(): string { + if (!this.fieldOptions || !this.fieldOptions.directives) { return ''; } + return this.fieldOptions.directives.reduce((acc, directive) => + `${acc}${directive.statement} `, '\n ').slice(0, -1); } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index 4102e506d013f..9b30e2ea9c33f 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -7,6 +7,7 @@ import { BaseTypeOptions, GraphqlType, ResolvableFieldOptions } from './schema-f * * @param definition - the variables and types that define this type * i.e. { string: GraphqlType, string: GraphqlType } + * @param directives - the directives for this object type * * @experimental */ @@ -15,6 +16,12 @@ export interface IntermediateTypeProps { * the attributes of this type */ readonly definition: { [key: string]: IField }; + /** + * the directives for this object type + * + * @default - no directives + */ + readonly directives?: Directive[]; } /** @@ -32,10 +39,17 @@ export class InterfaceType implements IIntermediateType { * the attributes of this type */ public readonly definition: { [key: string]: IField }; + /** + * the directives for this object type + * + * @default - no directives + */ + public readonly directives?: Directive[]; public constructor(name: string, props: IntermediateTypeProps) { this.name = name; this.definition = props.definition; + this.directives = props.directives; } /** @@ -59,13 +73,19 @@ export class InterfaceType implements IIntermediateType { * Generate the string of this object type */ public toString(): string { - let schemaAddition = `interface ${this.name} {\n`; - Object.keys(this.definition).forEach( (key) => { - const attribute = this.definition[key]; - const args = attribute.argsToString(); - schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; - }); - return `${schemaAddition}}`; + return Object.keys(this.definition).reduce((acc, key) => { + return `${acc}${this.fieldToString(key, this.definition[key])}`; + }, `interface ${this.name} ${this.generateDirectives()}{\n`) + '}'; + } + + /** + * Generate the field from its attributes + * + * @param key the key for this field + * @param field the attributes of this field + */ + protected fieldToString(key: string, field: IField): string { + return ` ${key}${field.argsToString()}: ${field.toString()}${field.directivesToString()}\n`; } /** @@ -77,6 +97,18 @@ export class InterfaceType implements IIntermediateType { public addField(fieldName: string, field: IField): void { this.definition[fieldName] = field; } + + /** + * Utility function to generate directives + * + * @param delimiter the separator betweeen directives + * @default - ' ' + */ + protected generateDirectives(delimiter?: string): string { + if (!this.directives) { return ''; } + return this.directives.reduce((acc, directive) => + `${acc}${directive.statement}${delimiter ?? ' '}`, ''); + } } /** @@ -96,12 +128,6 @@ export interface ObjectTypeProps extends IntermediateTypeProps { * @default - no interface types */ readonly interfaceTypes?: InterfaceType[]; - /** - * the directives for this object type - * - * @default - no directives - */ - readonly directives?: Directive[]; } /** @@ -116,12 +142,6 @@ export class ObjectType extends InterfaceType implements IIntermediateType { * @default - no interface types */ public readonly interfaceTypes?: InterfaceType[]; - /** - * the directives for this object type - * - * @default - no directives - */ - public readonly directives?: Directive[]; /** * The resolvers linked to this data source */ @@ -132,10 +152,10 @@ export class ObjectType extends InterfaceType implements IIntermediateType { definition: props.interfaceTypes?.reduce((def, interfaceType) => { return Object.assign({}, def, interfaceType.definition); }, props.definition) ?? props.definition, + directives: props.directives, }; super(name, options); this.interfaceTypes = props.interfaceTypes; - this.directives = props.directives; this.resolvers = []; Object.keys(this.definition).forEach((fieldName) => { @@ -161,36 +181,12 @@ export class ObjectType extends InterfaceType implements IIntermediateType { public toString(): string { let title = this.name; if (this.interfaceTypes && this.interfaceTypes.length) { - title = `${title} implements`; - this.interfaceTypes.map((interfaceType) => { - title = `${title} ${interfaceType.name},`; - }); - title = title.slice(0, -1); + title = this.interfaceTypes.reduce((acc, interfaceType) => + `${acc} ${interfaceType.name},`, `${title} implements`).slice(0, -1); } - const directives = this.generateDirectives(this.directives); - let schemaAddition = `type ${title} ${directives}{\n`; - Object.keys(this.definition).forEach( (key) => { - const attribute = this.definition[key]; - const args = attribute.argsToString(); - schemaAddition = `${schemaAddition} ${key}${args}: ${attribute.toString()}\n`; - }); - return `${schemaAddition}}`; - } - - /** - * Utility function to generate directives - * - * @param directives the directives of a given type - * @param delimiter the separator betweeen directives - * @default - ' ' - */ - private generateDirectives(directives?: Directive[], delimiter?: string): string { - let schemaAddition = ''; - if (!directives) { return schemaAddition; } - directives.map((directive) => { - schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; - }); - return schemaAddition; + return Object.keys(this.definition).reduce((acc, key) => { + return `${acc}${this.fieldToString(key, this.definition[key])}`; + }, `type ${title} ${this.generateDirectives()}{\n`) + '}'; } /** diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts new file mode 100644 index 0000000000000..2c1732eb62567 --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts @@ -0,0 +1,94 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; +import * as t from './scalar-type-defintions'; + +const iam = [appsync.Directive.iam()]; +const apiKey = [appsync.Directive.apiKey()]; +const oidc = [appsync.Directive.oidc()]; +const cognito_default = [appsync.Directive.cognito(['test', 'test2'])]; +const cognito_additional = [appsync.Directive.cognito(['test', 'test2'], true)]; + +const generateField = (directives: appsync.Directive[]): appsync.Field => { + return new appsync.Field({ + returnType: t.string, + directives, + }); +}; + +const generateRField = (directives: appsync.Directive[]): appsync.ResolvableField => { + return new appsync.ResolvableField({ + returnType: t.string, + directives, + }); +}; + +let stack: cdk.Stack; +let api: appsync.GraphQLApi; +beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + api = new appsync.GraphQLApi(stack, 'api', { + name: 'api', + }); +}); + +const testObjectType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[], tag: string): any => { + // WHEN + IApi.addObjectType('Test', { + definition: { + field: generateField(directives), + rfield: generateRField(directives), + }, + directives: directives, + }); + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `type Test ${tag} {\n field: String\n ${tag}\n rfield: String\n ${tag}\n}\n`, + }); +}; + +const testInterfaceType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[], tag: string): any => { + // WHEN + IApi.addInterfaceType('Test', { + definition: { + field: generateField(directives), + rfield: generateRField(directives), + }, + directives: directives, + }); + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `interface Test ${tag} {\n field: String\n ${tag}\n rfield: String\n ${tag}\n}\n`, + }); +}; + +describe('Basic Testing of Directives for Code-First', () => { + test('Iam directive configures in Object Type', () => { testObjectType(api, iam, '@aws_iam'); }); + + test('Iam directive configures in Interface Type', () => { testInterfaceType(api, iam, '@aws_iam'); }); + + test('Api Key directive configures in Object Type', () => { testObjectType(api, apiKey, '@aws_api_key'); }); + + test('Api Key directive configures in Interface Type', () => { testInterfaceType(api, apiKey, '@aws_api_key'); }); + + test('OIDC directive configures in Object Type', () => { testObjectType(api, oidc, '@aws_oidc'); }); + + test('OIDC directive configures in Interface Type', () => { testInterfaceType(api, oidc, '@aws_oidc'); }); + + test('Cognito as default directive configures in Object Type', () => { + testObjectType(api, cognito_default, '@aws_auth(cognito_groups: ["test", "test2"])'); + }); + + test('Cognito as default directive configures in Interface Type', () => { + testInterfaceType(api, cognito_default, '@aws_auth(cognito_groups: ["test", "test2"])'); + }); + + test('Cognito as additional directive configures in Object Type', () => { + testObjectType(api, cognito_additional, '@aws_cognito_user_pools(cognito_groups: ["test", "test2"])'); + }); + + test('Cognito as default directive configures in Interface Type', () => { + testInterfaceType(api, cognito_additional, '@aws_cognito_user_pools(cognito_groups: ["test", "test2"])'); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts index 981ec8b60a039..5b2c8393575bb 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts @@ -82,4 +82,48 @@ describe('testing InterfaceType properties', () => { Definition: `${out}`, }); }); + + test('Interface Type can generate Fields with Directives', () => { + // WHEN + const test = new appsync.InterfaceType('Test', { + definition: { + test: t.string, + }, + }); + test.addField('resolve', new appsync.Field({ + returnType: t.string, + directives: [appsync.Directive.iam()], + })); + + api.addType(test); + const out = 'interface Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Interface Type can generate ResolvableFields with Directives, but not the resolver', () => { + // WHEN + const test = new appsync.InterfaceType('Test', { + definition: { + test: t.string, + }, + }); + test.addField('resolve', new appsync.ResolvableField({ + returnType: t.string, + directives: [appsync.Directive.iam()], + dataSource: api.addNoneDataSource('none'), + })); + + api.addType(test); + const out = 'interface Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + expect(stack).not.toHaveResource('AWS::AppSync::Resolver'); + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts index a60a5242f6fe7..c7b0d5c6efe35 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts @@ -29,8 +29,8 @@ describe('testing Object Type properties', () => { directives: [appsync.Directive.custom('@test')], }); - api.addToSchema(baseTest.toString()); - api.addToSchema(objectTest.toString()); + api.addType(baseTest); + api.addType(objectTest); const gql_interface = 'interface baseTest {\n id: ID\n}\n'; const gql_object = 'type objectTest implements baseTest @test {\n id2: ID\n id: ID\n}\n'; const out = `${gql_interface}${gql_object}`; @@ -56,9 +56,9 @@ describe('testing Object Type properties', () => { }, }); - api.addToSchema(baseTest.toString()); - api.addToSchema(anotherTest.toString()); - api.addToSchema(objectTest.toString()); + api.addType(baseTest); + api.addType(anotherTest); + api.addType(objectTest); const gql_interface = 'interface baseTest {\n id: ID\n}\ninterface anotherTest {\n id2: ID\n}\n'; const gql_object = 'type objectTest implements anotherTest, baseTest {\n id3: ID\n id2: ID\n id: ID\n}\n'; @@ -83,7 +83,7 @@ describe('testing Object Type properties', () => { test: graphqlType, }, }); - api.addToSchema(test.toString()); + api.addType(test); const out = 'type Test {\n test: baseTest\n}\n'; // THEN @@ -107,7 +107,7 @@ describe('testing Object Type properties', () => { resolve: field, }, }); - api.addToSchema(test.toString()); + api.addType(test); const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; // THEN @@ -131,7 +131,7 @@ describe('testing Object Type properties', () => { resolve: field, }, }); - api.addToSchema(test.toString()); + api.addType(test); const out = 'type Test {\n test: String\n resolve(arg: Int): String\n}\n'; // THEN @@ -154,7 +154,7 @@ describe('testing Object Type properties', () => { }), }, }); - api.addToSchema(test.toString()); + api.addType(test); // THEN expect(stack).toHaveResourceLike('AWS::AppSync::Resolver', { @@ -168,9 +168,7 @@ describe('testing Object Type properties', () => { const field = new appsync.ResolvableField({ returnType: t.string, dataSource: api.addNoneDataSource('none'), - args: { - arg: t.int, - }, + args: { arg: t.int }, }); const test = new appsync.ObjectType('Test', { definition: { @@ -181,7 +179,7 @@ describe('testing Object Type properties', () => { // test.addField('resolve', field); test.addField('dynamic', t.string); - api.addToSchema(test.toString()); + api.addType(test); const out = 'type Test {\n test: String\n resolve(arg: Int): String\n dynamic: String\n}\n'; // THEN @@ -191,31 +189,42 @@ describe('testing Object Type properties', () => { expect(stack).toHaveResource('AWS::AppSync::Resolver'); }); - test('Object Type can dynamically add Fields', () => { + test('Object Type can generate Fields with Directives', () => { // WHEN - const garbage = new appsync.InterfaceType('Garbage', { + const test = new appsync.ObjectType('Test', { definition: { - garbage: t.string, + test: t.string, }, }); + test.addField('resolve', new appsync.Field({ + returnType: t.string, + directives: [appsync.Directive.iam()], + })); + + api.addType(test); + const out = 'type Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { + Definition: `${out}`, + }); + }); + + test('Object Type can generate ResolvableFields with Directives', () => { + // WHEN const test = new appsync.ObjectType('Test', { definition: { test: t.string, }, }); - const field = new appsync.ResolvableField({ - returnType: garbage.attribute(), + test.addField('resolve', new appsync.ResolvableField({ + returnType: t.string, + directives: [appsync.Directive.iam()], dataSource: api.addNoneDataSource('none'), - args: { - arg: garbage.attribute(), - }, - }); - test.addField('resolve', field); - // test.addField('resolve', field); - test.addField('dynamic', garbage.attribute()); + })); - api.addToSchema(test.toString()); - const out = 'type Test {\n test: String\n resolve(arg: Garbage): Garbage\n dynamic: Garbage\n}\n'; + api.addType(test); + const out = 'type Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { From 5988828157be6a9f50dc93a0f471c36213f3d66c Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 25 Aug 2020 15:22:09 -0700 Subject: [PATCH 72/78] test custom --- .../@aws-cdk/aws-appsync/test/appsync-directives.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts index 2c1732eb62567..059c7a6c1d62d 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts @@ -8,6 +8,7 @@ const apiKey = [appsync.Directive.apiKey()]; const oidc = [appsync.Directive.oidc()]; const cognito_default = [appsync.Directive.cognito(['test', 'test2'])]; const cognito_additional = [appsync.Directive.cognito(['test', 'test2'], true)]; +const custom = [appsync.Directive.custom('custom')]; const generateField = (directives: appsync.Directive[]): appsync.Field => { return new appsync.Field({ @@ -88,7 +89,11 @@ describe('Basic Testing of Directives for Code-First', () => { testObjectType(api, cognito_additional, '@aws_cognito_user_pools(cognito_groups: ["test", "test2"])'); }); - test('Cognito as default directive configures in Interface Type', () => { - testInterfaceType(api, cognito_additional, '@aws_cognito_user_pools(cognito_groups: ["test", "test2"])'); + test('Custom directive configures in Object Type', () => { + testObjectType(api, custom, 'custom'); + }); + + test('Custom directive configures in Interface Type', () => { + testInterfaceType(api, custom, 'custom'); }); }); \ No newline at end of file From 7d1acce8a8bcdca187e59a0548595cb9428a56f1 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Wed, 26 Aug 2020 17:16:22 -0700 Subject: [PATCH 73/78] update directives --- .../@aws-cdk/aws-appsync/test/appsync-directives.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts index 059c7a6c1d62d..8871801f765c8 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts @@ -36,13 +36,13 @@ beforeEach(() => { const testObjectType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[], tag: string): any => { // WHEN - IApi.addObjectType('Test', { + IApi.addType(new appsync.ObjectType('Test', { definition: { field: generateField(directives), rfield: generateRField(directives), }, directives: directives, - }); + })); // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { Definition: `type Test ${tag} {\n field: String\n ${tag}\n rfield: String\n ${tag}\n}\n`, @@ -51,13 +51,13 @@ const testObjectType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[ const testInterfaceType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[], tag: string): any => { // WHEN - IApi.addInterfaceType('Test', { + IApi.addType(new appsync.InterfaceType('Test', { definition: { field: generateField(directives), rfield: generateRField(directives), }, directives: directives, - }); + })); // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { Definition: `interface Test ${tag} {\n field: String\n ${tag}\n rfield: String\n ${tag}\n}\n`, From d6c10dd71194303ee0d4652830c7e30b0b9c1436 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Thu, 27 Aug 2020 11:10:09 -0700 Subject: [PATCH 74/78] update readme --- packages/@aws-cdk/aws-appsync/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 88f059fc30b95..c93e1ba006d25 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -369,6 +369,22 @@ More concretely, GraphQL Types are simply the types appended to variables. Referencing the object type `Demo` in the previous example, the GraphQL Types is `String!` and is applied to both the names `id` and `version`. +### Directives + +`Directives` are attached to a field or type and affect the execution of queries, +mutations, and types. With AppSync, we use `Directives` to signal authentication. +CDK provides static functions to add directives to your Schema. + +- `Directive.iam()` sets a type or field's authorization to be validated through `Iam` +- `Directive.apiKey()` sets a type or field's authorization to be validated through a `Api Key` +- `Directive.oidc()` sets a type or field's authorization to be validated through `OpenID Connect` +- `Directive.cognito(groups: string[], isAdditional: boolean)` sets a type or field's + authorization to be validated through `Cognito User Pools` + - `groups` the name of the cognito groups to give access to + - `isAdditional` if using cognito authorization as an additional mode, set this to be true + +To learn more about authorization and directives, read these docs [here](https://docs.aws.amazon.com/appsync/latest/devguide/security.html). + ### Field and Resolvable Fields While `GraphqlType` is a base implementation for GraphQL fields, we have abstractions From f3c77e35237abb980e50ab300fb39eb72a1f3934 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 31 Aug 2020 14:56:35 -0700 Subject: [PATCH 75/78] refactor(appsync): graphQLApi to graphqlApi for better snakecasing --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 14 +++--- packages/@aws-cdk/aws-appsync/lib/schema.ts | 4 +- packages/@aws-cdk/aws-appsync/package.json | 3 +- .../aws-appsync/test/appsync-auth.test.ts | 46 +++++++++---------- .../test/appsync-code-first.test.ts | 16 +++---- .../aws-appsync/test/appsync-dynamodb.test.ts | 10 ++-- .../aws-appsync/test/appsync-grant.test.ts | 4 +- .../aws-appsync/test/appsync-http.test.ts | 10 ++-- .../test/appsync-interface-type.test.ts | 4 +- .../aws-appsync/test/appsync-lambda.test.ts | 10 ++-- .../aws-appsync/test/appsync-none.test.ts | 10 ++-- .../test/appsync-object-type.test.ts | 4 +- .../test/appsync-scalar-type.test.ts | 4 +- .../aws-appsync/test/appsync-schema.test.ts | 24 +++++----- .../@aws-cdk/aws-appsync/test/appsync.test.ts | 6 +-- .../aws-appsync/test/integ.api-import.ts | 8 ++-- .../aws-appsync/test/integ.graphql-iam.ts | 8 ++-- .../aws-appsync/test/integ.graphql-schema.ts | 2 +- .../aws-appsync/test/integ.graphql.ts | 4 +- 19 files changed, 95 insertions(+), 96 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 4251336f017ea..052e3ecd53392 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -206,7 +206,7 @@ export interface LogConfig { /** * Properties for an AppSync GraphQL API */ -export interface GraphQLApiProps { +export interface GraphqlApiProps { /** * the name of the GraphQL API */ @@ -293,7 +293,7 @@ export class IamResource { * * @param api The GraphQL API to give permissions */ - public resourceArns(api: GraphQLApi): string[] { + public resourceArns(api: GraphqlApi): string[] { return this.arns.map((arn) => Stack.of(api).formatArn({ service: 'appsync', resource: `apis/${api.apiId}`, @@ -325,7 +325,7 @@ export interface GraphqlApiAttributes { * * @resource AWS::AppSync::GraphQLApi */ -export class GraphQLApi extends GraphqlApiBase { +export class GraphqlApi extends GraphqlApiBase { /** * Import a GraphQL API through this function * @@ -362,9 +362,9 @@ export class GraphQLApi extends GraphqlApiBase { /** * the URL of the endpoint created by AppSync * - * @attribute + * @attribute GraphQlUrl */ - public readonly graphQlUrl: string; + public readonly graphqlUrl: string; /** * the name of the API @@ -387,7 +387,7 @@ export class GraphQLApi extends GraphqlApiBase { private api: CfnGraphQLApi; private apiKeyResource?: CfnApiKey; - constructor(scope: Construct, id: string, props: GraphQLApiProps) { + constructor(scope: Construct, id: string, props: GraphqlApiProps) { super(scope, id); const defaultMode = props.authorizationConfig?.defaultAuthorization ?? @@ -409,7 +409,7 @@ export class GraphQLApi extends GraphqlApiBase { this.apiId = this.api.attrApiId; this.arn = this.api.attrArn; - this.graphQlUrl = this.api.attrGraphQlUrl; + this.graphqlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; this.schema = props.schema ?? new Schema(); this.schemaResource = this.schema.bind(this); diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index f7751f38a3182..0efb28074fc27 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import { Lazy } from '@aws-cdk/core'; import { CfnGraphQLSchema } from './appsync.generated'; -import { GraphQLApi } from './graphqlapi'; +import { GraphqlApi } from './graphqlapi'; import { SchemaMode, shapeAddition } from './private'; import { IIntermediateType } from './schema-base'; import { ResolvableField } from './schema-field'; @@ -72,7 +72,7 @@ export class Schema { * * @param api The binding GraphQL Api */ - public bind(api: GraphQLApi): CfnGraphQLSchema { + public bind(api: GraphqlApi): CfnGraphQLSchema { if (!this.schema) { this.schema = new CfnGraphQLSchema(api, 'Schema', { apiId: api.apiId, diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 7c38409b37325..6726dc41c43a4 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -98,8 +98,7 @@ "no-unused-type:@aws-cdk/aws-appsync.ApiKeyConfig", "no-unused-type:@aws-cdk/aws-appsync.UserPoolConfig", "no-unused-type:@aws-cdk/aws-appsync.UserPoolDefaultAction", - "props-physical-name:@aws-cdk/aws-appsync.GraphQLApiProps", - "from-method:@aws-cdk/aws-appsync.GraphQLApi" + "props-physical-name:@aws-cdk/aws-appsync.GraphqlApiProps" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts index fabde1d59c95e..5815908198feb 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts @@ -13,7 +13,7 @@ beforeEach(() => { describe('AppSync API Key Authorization', () => { test('AppSync creates default api key', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -24,7 +24,7 @@ describe('AppSync API Key Authorization', () => { test('AppSync creates api key from additionalAuthorizationModes', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -41,7 +41,7 @@ describe('AppSync API Key Authorization', () => { test('AppSync does not create unspecified api key from additionalAuthorizationModes', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -55,7 +55,7 @@ describe('AppSync API Key Authorization', () => { test('appsync does not create unspecified api key with empty additionalAuthorizationModes', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -70,7 +70,7 @@ describe('AppSync API Key Authorization', () => { test('appsync creates configured api key with additionalAuthorizationModes', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -90,7 +90,7 @@ describe('AppSync API Key Authorization', () => { test('appsync creates configured api key with additionalAuthorizationModes (not as first element)', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -117,7 +117,7 @@ describe('AppSync API Key Authorization', () => { test('appsync fails when empty default and API_KEY in additional', () => { // THEN expect(() => { - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -132,7 +132,7 @@ describe('AppSync API Key Authorization', () => { test('appsync fails when multiple API_KEY auth modes', () => { // THEN expect(() => { - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -148,7 +148,7 @@ describe('AppSync API Key Authorization', () => { test('appsync fails when multiple API_KEY auth modes in additionalXxx', () => { // THEN expect(() => { - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -166,7 +166,7 @@ describe('AppSync API Key Authorization', () => { describe('AppSync IAM Authorization', () => { test('Iam authorization configurable in default authorization', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -182,7 +182,7 @@ describe('AppSync IAM Authorization', () => { test('Iam authorization configurable in additional authorization', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -199,7 +199,7 @@ describe('AppSync IAM Authorization', () => { test('appsync fails when multiple iam auth modes', () => { // THEN expect(() => { - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -213,7 +213,7 @@ describe('AppSync IAM Authorization', () => { test('appsync fails when multiple IAM auth modes in additionalXxx', () => { // THEN expect(() => { - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -234,7 +234,7 @@ describe('AppSync User Pool Authorization', () => { }); test('User Pool authorization configurable in default authorization has default configuration', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -258,7 +258,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool authorization configurable in default authorization', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -287,7 +287,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool authorization configurable in additional authorization has default configuration', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -312,7 +312,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool property defaultAction does not configure when in additional auth', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -342,7 +342,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool property defaultAction does not configure when in additional auth', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -399,7 +399,7 @@ describe('AppSync User Pool Authorization', () => { describe('AppSync OIDC Authorization', () => { test('OIDC authorization configurable in default authorization has default configuration', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -421,7 +421,7 @@ describe('AppSync OIDC Authorization', () => { test('User Pool authorization configurable in default authorization', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -451,7 +451,7 @@ describe('AppSync OIDC Authorization', () => { test('OIDC authorization configurable in additional authorization has default configuration', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -475,7 +475,7 @@ describe('AppSync OIDC Authorization', () => { test('User Pool authorization configurable in additional authorization', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -507,7 +507,7 @@ describe('AppSync OIDC Authorization', () => { test('User Pool authorization configurable in with multiple authorization', () => { // WHEN - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 0b39cab4c4c24..98460fd8be00d 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -10,10 +10,10 @@ beforeEach(() => { }); describe('code-first implementation through GraphQL Api functions`', () => { - let api: appsync.GraphQLApi; + let api: appsync.GraphqlApi; beforeEach(() => { // GIVEN - api = new appsync.GraphQLApi(stack, 'api', { + api = new appsync.GraphqlApi(stack, 'api', { name: 'api', }); }); @@ -164,7 +164,7 @@ describe('code-first implementation through Schema functions`', () => { schema.addType(test); test.addField('dupid', t.dup_id); - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema, }); @@ -190,7 +190,7 @@ describe('code-first implementation through Schema functions`', () => { schema.addType(test); test.addField('dupid', t.dup_id); - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema, }); @@ -215,7 +215,7 @@ describe('code-first implementation through Schema functions`', () => { }, })); - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema, }); @@ -241,7 +241,7 @@ describe('code-first implementation through Schema functions`', () => { })); test.addField('dupid', t.dup_id); - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema, }); @@ -265,7 +265,7 @@ describe('code-first implementation through Schema functions`', () => { dupid: t.dup_id, }, })); - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema, }); @@ -290,7 +290,7 @@ describe('code-first implementation through Schema functions`', () => { })); test.addField('dupid', t.dup_id); - new appsync.GraphQLApi(stack, 'api', { + new appsync.GraphqlApi(stack, 'api', { name: 'api', schema, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts index e54a9576396d1..07565edd1fba5 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts @@ -10,10 +10,10 @@ function joined(str: string): string { // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'baseApi', { + api = new appsync.GraphqlApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -75,7 +75,7 @@ describe('DynamoDb Data Source configuration', () => { expect(() => { api.addDynamoDbDataSource('ds', table); api.addDynamoDbDataSource('ds', table); - }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); }); }); @@ -160,7 +160,7 @@ describe('adding DynamoDb data source from imported api', () => { test('imported api can add DynamoDbDataSource from id', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addDynamoDbDataSource('ds', table); @@ -174,7 +174,7 @@ describe('adding DynamoDb data source from imported api', () => { test('imported api can add DynamoDbDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); 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 44251cd7fabee..d59b7c5d363cb 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts @@ -6,14 +6,14 @@ import * as appsync from '../lib'; let stack: cdk.Stack; let role: iam.Role; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); role = new iam.Role(stack, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); - api = new appsync.GraphQLApi(stack, 'API', { + api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), authorizationConfig: { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts index 899a76ebd19f4..6bc237e0f5c71 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts @@ -5,11 +5,11 @@ import * as appsync from '../lib'; // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; let endpoint: string; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'baseApi', { + api = new appsync.GraphqlApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -62,14 +62,14 @@ describe('Http Data Source configuration', () => { expect(() => { api.addHttpDataSource('ds', endpoint); api.addHttpDataSource('ds', endpoint); - }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); }); }); describe('adding http data source from imported api', () => { test('imported api can add HttpDataSource from id', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addHttpDataSource('ds', endpoint); @@ -83,7 +83,7 @@ describe('adding http data source from imported api', () => { test('imported api can add HttpDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts index 981ec8b60a039..644e7fae42354 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts @@ -4,11 +4,11 @@ import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'api', { + api = new appsync.GraphqlApi(stack, 'api', { name: 'api', }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts index a67fd4b1691b7..0cc8396382017 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts @@ -6,10 +6,10 @@ import * as appsync from '../lib'; // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'baseApi', { + api = new appsync.GraphqlApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -70,7 +70,7 @@ describe('Lambda Data Source configuration', () => { expect(() => { api.addLambdaDataSource('ds', func); api.addLambdaDataSource('ds', func); - }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); }); }); @@ -86,7 +86,7 @@ describe('adding lambda data source from imported api', () => { test('imported api can add LambdaDbDataSource from id', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addLambdaDataSource('ds', func); @@ -100,7 +100,7 @@ describe('adding lambda data source from imported api', () => { test('imported api can add LambdaDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts index f2b52c7dfba03..3985cebc30719 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts @@ -5,10 +5,10 @@ import * as appsync from '../lib'; // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'baseApi', { + api = new appsync.GraphqlApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -60,7 +60,7 @@ describe('None Data Source configuration', () => { expect(() => { api.addNoneDataSource('ds'); api.addNoneDataSource('ds'); - }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); }); test('appsync errors when creating multiple none data sources with same name configuration', () => { @@ -75,7 +75,7 @@ describe('None Data Source configuration', () => { describe('adding none data source from imported api', () => { test('imported api can add NoneDataSource from id', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addNoneDataSource('none'); @@ -89,7 +89,7 @@ describe('adding none data source from imported api', () => { test('imported api can add NoneDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts index a60a5242f6fe7..02f0d9b43ff57 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts @@ -4,11 +4,11 @@ import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'api', { + api = new appsync.GraphqlApi(stack, 'api', { name: 'api', }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts index 64fddd55bc3d3..312d7a3784b98 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts @@ -4,11 +4,11 @@ import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'api', { + api = new appsync.GraphqlApi(stack, 'api', { name: 'api', }); }); 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 f60d63f1dec5c..95fe1ac7c500a 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -36,7 +36,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` produces empty schema definition', () => { // WHEN - new appsync.GraphQLApi(stack, 'API', { + new appsync.GraphqlApi(stack, 'API', { name: 'demo', }); @@ -48,7 +48,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` generates correct schema with addToSchema', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', }); api.addType(type); @@ -63,7 +63,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for api to addQuery', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', }); api.addQuery('test', new appsync.ResolvableField({ @@ -79,7 +79,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for schema to addQuery', () => { // WHEN const schema = new appsync.Schema(); - new appsync.GraphQLApi(stack, 'API', { + new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema, }); @@ -95,7 +95,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for api to addMutation', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', }); api.addMutation('test', new appsync.ResolvableField({ @@ -111,7 +111,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for schema to addMutation', () => { // WHEN const schema = new appsync.Schema(); - new appsync.GraphQLApi(stack, 'API', { + new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema, }); @@ -130,7 +130,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` produces correct output', () => { // WHEN - new appsync.GraphQLApi(stack, 'API', { + new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -143,7 +143,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addType for object is called', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -158,7 +158,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addType for interface is called', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -173,7 +173,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addToSchema is called', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -186,7 +186,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addQuery is called', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -199,7 +199,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addMutation is called', () => { // WHEN - const api = new appsync.GraphQLApi(stack, 'API', { + const api = new appsync.GraphqlApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index 9dc6808a672e1..debdfa71ce4f0 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -4,10 +4,10 @@ import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; let stack: cdk.Stack; -let api: appsync.GraphQLApi; +let api: appsync.GraphqlApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'api', { + api = new appsync.GraphqlApi(stack, 'api', { authorizationConfig: {}, name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), @@ -61,7 +61,7 @@ test('appsync should configure resolver as unit when pipelineConfig is empty arr test('when xray is enabled should not throw an Error', () => { // WHEN - new appsync.GraphQLApi(stack, 'api-x-ray', { + new appsync.GraphqlApi(stack, 'api-x-ray', { authorizationConfig: {}, name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts index b36c6a1bef7b1..8781f83fe117e 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts @@ -11,7 +11,7 @@ import * as appsync from '../lib'; * * Stack verification steps: * Install dependencies and deploy integration test. Check if data sources are - * connected to the graphQL Api + * connected to the GraphQL Api * * -- cdk deploy --app 'node integ.api-import.js' stack -- start -- * -- aws appsync list-graphql-apis -- obtain api id -- @@ -22,13 +22,13 @@ import * as appsync from '../lib'; const app = new cdk.App(); const baseStack = new cdk.Stack(app, 'baseStack'); -const baseApi = new appsync.GraphQLApi(baseStack, 'baseApi', { +const baseApi = new appsync.GraphqlApi(baseStack, 'baseApi', { name: 'baseApi', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); const stack = new cdk.Stack(app, 'stack'); -const api = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'Api', { +const api = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'Api', { graphqlApiId: `${baseApi.apiId}`, }); @@ -57,7 +57,7 @@ testDS.createResolver({ responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(), }); -const api2 = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'api2', { +const api2 = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'api2', { graphqlApiId: baseApi.apiId, graphqlApiArn: baseApi.arn, }); 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 0d4c95a10ea3f..185fc8ef3e729 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts @@ -6,7 +6,7 @@ import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import { AuthorizationType, - GraphQLApi, + GraphqlApi, MappingTemplate, PrimaryKey, UserPoolDefaultAction, @@ -36,7 +36,7 @@ const userPool = new UserPool(stack, 'Pool', { userPoolName: 'myPool', }); -const api = new GraphQLApi(stack, 'Api', { +const api = new GraphqlApi(stack, 'Api', { name: 'Integ_Test_IAM', schema: Schema.fromAsset(join(__dirname, 'integ.graphql-iam.graphql')), authorizationConfig: { @@ -98,14 +98,14 @@ new Function(stack, 'testQuery', { code: Code.fromAsset('verify'), handler: 'iam-query.handler', runtime: Runtime.NODEJS_12_X, - environment: { APPSYNC_ENDPOINT: api.graphQlUrl }, + environment: { APPSYNC_ENDPOINT: api.graphqlUrl }, role: lambdaIAM, }); new Function(stack, 'testFail', { code: Code.fromAsset('verify'), handler: 'iam-query.handler', runtime: Runtime.NODEJS_12_X, - environment: { APPSYNC_ENDPOINT: api.graphQlUrl }, + environment: { APPSYNC_ENDPOINT: api.graphqlUrl }, }); app.synth(); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 8bde313c6f724..8cb3daafab11a 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -29,7 +29,7 @@ const node = schema.addType(new appsync.InterfaceType('Node', { }, })); -const api = new appsync.GraphQLApi(stack, 'code-first-api', { +const api = new appsync.GraphqlApi(stack, 'code-first-api', { name: 'api', schema: schema, }); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 9882fead1cf12..1de80995c90a0 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -4,7 +4,7 @@ import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb'; import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import { AuthorizationType, - GraphQLApi, + GraphqlApi, KeyCondition, MappingTemplate, PrimaryKey, @@ -33,7 +33,7 @@ const userPool = new UserPool(stack, 'Pool', { userPoolName: 'myPool', }); -const api = new GraphQLApi(stack, 'Api', { +const api = new GraphqlApi(stack, 'Api', { name: 'demoapi', schema: Schema.fromAsset(join(__dirname, 'integ.graphql.graphql')), authorizationConfig: { From 51edc01b01fb75896770ca1d28647f2a12be755f Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 31 Aug 2020 15:04:47 -0700 Subject: [PATCH 76/78] Revert "refactor(appsync): graphQLApi to graphqlApi for better snakecasing" This reverts commit f3c77e35237abb980e50ab300fb39eb72a1f3934. --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 14 +++--- packages/@aws-cdk/aws-appsync/lib/schema.ts | 4 +- packages/@aws-cdk/aws-appsync/package.json | 3 +- .../aws-appsync/test/appsync-auth.test.ts | 46 +++++++++---------- .../test/appsync-code-first.test.ts | 16 +++---- .../aws-appsync/test/appsync-dynamodb.test.ts | 10 ++-- .../aws-appsync/test/appsync-grant.test.ts | 4 +- .../aws-appsync/test/appsync-http.test.ts | 10 ++-- .../test/appsync-interface-type.test.ts | 4 +- .../aws-appsync/test/appsync-lambda.test.ts | 10 ++-- .../aws-appsync/test/appsync-none.test.ts | 10 ++-- .../test/appsync-object-type.test.ts | 4 +- .../test/appsync-scalar-type.test.ts | 4 +- .../aws-appsync/test/appsync-schema.test.ts | 24 +++++----- .../@aws-cdk/aws-appsync/test/appsync.test.ts | 6 +-- .../aws-appsync/test/integ.api-import.ts | 8 ++-- .../aws-appsync/test/integ.graphql-iam.ts | 8 ++-- .../aws-appsync/test/integ.graphql-schema.ts | 2 +- .../aws-appsync/test/integ.graphql.ts | 4 +- 19 files changed, 96 insertions(+), 95 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 052e3ecd53392..4251336f017ea 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -206,7 +206,7 @@ export interface LogConfig { /** * Properties for an AppSync GraphQL API */ -export interface GraphqlApiProps { +export interface GraphQLApiProps { /** * the name of the GraphQL API */ @@ -293,7 +293,7 @@ export class IamResource { * * @param api The GraphQL API to give permissions */ - public resourceArns(api: GraphqlApi): string[] { + public resourceArns(api: GraphQLApi): string[] { return this.arns.map((arn) => Stack.of(api).formatArn({ service: 'appsync', resource: `apis/${api.apiId}`, @@ -325,7 +325,7 @@ export interface GraphqlApiAttributes { * * @resource AWS::AppSync::GraphQLApi */ -export class GraphqlApi extends GraphqlApiBase { +export class GraphQLApi extends GraphqlApiBase { /** * Import a GraphQL API through this function * @@ -362,9 +362,9 @@ export class GraphqlApi extends GraphqlApiBase { /** * the URL of the endpoint created by AppSync * - * @attribute GraphQlUrl + * @attribute */ - public readonly graphqlUrl: string; + public readonly graphQlUrl: string; /** * the name of the API @@ -387,7 +387,7 @@ export class GraphqlApi extends GraphqlApiBase { private api: CfnGraphQLApi; private apiKeyResource?: CfnApiKey; - constructor(scope: Construct, id: string, props: GraphqlApiProps) { + constructor(scope: Construct, id: string, props: GraphQLApiProps) { super(scope, id); const defaultMode = props.authorizationConfig?.defaultAuthorization ?? @@ -409,7 +409,7 @@ export class GraphqlApi extends GraphqlApiBase { this.apiId = this.api.attrApiId; this.arn = this.api.attrArn; - this.graphqlUrl = this.api.attrGraphQlUrl; + this.graphQlUrl = this.api.attrGraphQlUrl; this.name = this.api.name; this.schema = props.schema ?? new Schema(); this.schemaResource = this.schema.bind(this); diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index 0efb28074fc27..f7751f38a3182 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import { Lazy } from '@aws-cdk/core'; import { CfnGraphQLSchema } from './appsync.generated'; -import { GraphqlApi } from './graphqlapi'; +import { GraphQLApi } from './graphqlapi'; import { SchemaMode, shapeAddition } from './private'; import { IIntermediateType } from './schema-base'; import { ResolvableField } from './schema-field'; @@ -72,7 +72,7 @@ export class Schema { * * @param api The binding GraphQL Api */ - public bind(api: GraphqlApi): CfnGraphQLSchema { + public bind(api: GraphQLApi): CfnGraphQLSchema { if (!this.schema) { this.schema = new CfnGraphQLSchema(api, 'Schema', { apiId: api.apiId, diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index 6726dc41c43a4..7c38409b37325 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -98,7 +98,8 @@ "no-unused-type:@aws-cdk/aws-appsync.ApiKeyConfig", "no-unused-type:@aws-cdk/aws-appsync.UserPoolConfig", "no-unused-type:@aws-cdk/aws-appsync.UserPoolDefaultAction", - "props-physical-name:@aws-cdk/aws-appsync.GraphqlApiProps" + "props-physical-name:@aws-cdk/aws-appsync.GraphQLApiProps", + "from-method:@aws-cdk/aws-appsync.GraphQLApi" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts index 5815908198feb..fabde1d59c95e 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts @@ -13,7 +13,7 @@ beforeEach(() => { describe('AppSync API Key Authorization', () => { test('AppSync creates default api key', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -24,7 +24,7 @@ describe('AppSync API Key Authorization', () => { test('AppSync creates api key from additionalAuthorizationModes', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -41,7 +41,7 @@ describe('AppSync API Key Authorization', () => { test('AppSync does not create unspecified api key from additionalAuthorizationModes', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -55,7 +55,7 @@ describe('AppSync API Key Authorization', () => { test('appsync does not create unspecified api key with empty additionalAuthorizationModes', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -70,7 +70,7 @@ describe('AppSync API Key Authorization', () => { test('appsync creates configured api key with additionalAuthorizationModes', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -90,7 +90,7 @@ describe('AppSync API Key Authorization', () => { test('appsync creates configured api key with additionalAuthorizationModes (not as first element)', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -117,7 +117,7 @@ describe('AppSync API Key Authorization', () => { test('appsync fails when empty default and API_KEY in additional', () => { // THEN expect(() => { - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -132,7 +132,7 @@ describe('AppSync API Key Authorization', () => { test('appsync fails when multiple API_KEY auth modes', () => { // THEN expect(() => { - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -148,7 +148,7 @@ describe('AppSync API Key Authorization', () => { test('appsync fails when multiple API_KEY auth modes in additionalXxx', () => { // THEN expect(() => { - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -166,7 +166,7 @@ describe('AppSync API Key Authorization', () => { describe('AppSync IAM Authorization', () => { test('Iam authorization configurable in default authorization', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -182,7 +182,7 @@ describe('AppSync IAM Authorization', () => { test('Iam authorization configurable in additional authorization', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -199,7 +199,7 @@ describe('AppSync IAM Authorization', () => { test('appsync fails when multiple iam auth modes', () => { // THEN expect(() => { - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -213,7 +213,7 @@ describe('AppSync IAM Authorization', () => { test('appsync fails when multiple IAM auth modes in additionalXxx', () => { // THEN expect(() => { - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -234,7 +234,7 @@ describe('AppSync User Pool Authorization', () => { }); test('User Pool authorization configurable in default authorization has default configuration', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -258,7 +258,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool authorization configurable in default authorization', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -287,7 +287,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool authorization configurable in additional authorization has default configuration', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -312,7 +312,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool property defaultAction does not configure when in additional auth', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -342,7 +342,7 @@ describe('AppSync User Pool Authorization', () => { test('User Pool property defaultAction does not configure when in additional auth', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -399,7 +399,7 @@ describe('AppSync User Pool Authorization', () => { describe('AppSync OIDC Authorization', () => { test('OIDC authorization configurable in default authorization has default configuration', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -421,7 +421,7 @@ describe('AppSync OIDC Authorization', () => { test('User Pool authorization configurable in default authorization', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -451,7 +451,7 @@ describe('AppSync OIDC Authorization', () => { test('OIDC authorization configurable in additional authorization has default configuration', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -475,7 +475,7 @@ describe('AppSync OIDC Authorization', () => { test('User Pool authorization configurable in additional authorization', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { @@ -507,7 +507,7 @@ describe('AppSync OIDC Authorization', () => { test('User Pool authorization configurable in with multiple authorization', () => { // WHEN - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), authorizationConfig: { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts index 98460fd8be00d..0b39cab4c4c24 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-code-first.test.ts @@ -10,10 +10,10 @@ beforeEach(() => { }); describe('code-first implementation through GraphQL Api functions`', () => { - let api: appsync.GraphqlApi; + let api: appsync.GraphQLApi; beforeEach(() => { // GIVEN - api = new appsync.GraphqlApi(stack, 'api', { + api = new appsync.GraphQLApi(stack, 'api', { name: 'api', }); }); @@ -164,7 +164,7 @@ describe('code-first implementation through Schema functions`', () => { schema.addType(test); test.addField('dupid', t.dup_id); - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema, }); @@ -190,7 +190,7 @@ describe('code-first implementation through Schema functions`', () => { schema.addType(test); test.addField('dupid', t.dup_id); - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema, }); @@ -215,7 +215,7 @@ describe('code-first implementation through Schema functions`', () => { }, })); - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema, }); @@ -241,7 +241,7 @@ describe('code-first implementation through Schema functions`', () => { })); test.addField('dupid', t.dup_id); - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema, }); @@ -265,7 +265,7 @@ describe('code-first implementation through Schema functions`', () => { dupid: t.dup_id, }, })); - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema, }); @@ -290,7 +290,7 @@ describe('code-first implementation through Schema functions`', () => { })); test.addField('dupid', t.dup_id); - new appsync.GraphqlApi(stack, 'api', { + new appsync.GraphQLApi(stack, 'api', { name: 'api', schema, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts index 07565edd1fba5..e54a9576396d1 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-dynamodb.test.ts @@ -10,10 +10,10 @@ function joined(str: string): string { // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'baseApi', { + api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -75,7 +75,7 @@ describe('DynamoDb Data Source configuration', () => { expect(() => { api.addDynamoDbDataSource('ds', table); api.addDynamoDbDataSource('ds', table); - }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); }); }); @@ -160,7 +160,7 @@ describe('adding DynamoDb data source from imported api', () => { test('imported api can add DynamoDbDataSource from id', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addDynamoDbDataSource('ds', table); @@ -174,7 +174,7 @@ describe('adding DynamoDb data source from imported api', () => { test('imported api can add DynamoDbDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); 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 d59b7c5d363cb..44251cd7fabee 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-grant.test.ts @@ -6,14 +6,14 @@ import * as appsync from '../lib'; let stack: cdk.Stack; let role: iam.Role; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); role = new iam.Role(stack, 'Role', { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), }); - api = new appsync.GraphqlApi(stack, 'API', { + api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), authorizationConfig: { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts index 6bc237e0f5c71..899a76ebd19f4 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-http.test.ts @@ -5,11 +5,11 @@ import * as appsync from '../lib'; // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; let endpoint: string; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'baseApi', { + api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -62,14 +62,14 @@ describe('Http Data Source configuration', () => { expect(() => { api.addHttpDataSource('ds', endpoint); api.addHttpDataSource('ds', endpoint); - }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); }); }); describe('adding http data source from imported api', () => { test('imported api can add HttpDataSource from id', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addHttpDataSource('ds', endpoint); @@ -83,7 +83,7 @@ describe('adding http data source from imported api', () => { test('imported api can add HttpDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts index 644e7fae42354..981ec8b60a039 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts @@ -4,11 +4,11 @@ import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'api', { + api = new appsync.GraphQLApi(stack, 'api', { name: 'api', }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts index 0cc8396382017..a67fd4b1691b7 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-lambda.test.ts @@ -6,10 +6,10 @@ import * as appsync from '../lib'; // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'baseApi', { + api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -70,7 +70,7 @@ describe('Lambda Data Source configuration', () => { expect(() => { api.addLambdaDataSource('ds', func); api.addLambdaDataSource('ds', func); - }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); }); }); @@ -86,7 +86,7 @@ describe('adding lambda data source from imported api', () => { test('imported api can add LambdaDbDataSource from id', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addLambdaDataSource('ds', func); @@ -100,7 +100,7 @@ describe('adding lambda data source from imported api', () => { test('imported api can add LambdaDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts index 3985cebc30719..f2b52c7dfba03 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-none.test.ts @@ -5,10 +5,10 @@ import * as appsync from '../lib'; // GLOBAL GIVEN let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'baseApi', { + api = new appsync.GraphQLApi(stack, 'baseApi', { name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); @@ -60,7 +60,7 @@ describe('None Data Source configuration', () => { expect(() => { api.addNoneDataSource('ds'); api.addNoneDataSource('ds'); - }).toThrow("There is already a Construct with name 'ds' in GraphqlApi [baseApi]"); + }).toThrow("There is already a Construct with name 'ds' in GraphQLApi [baseApi]"); }); test('appsync errors when creating multiple none data sources with same name configuration', () => { @@ -75,7 +75,7 @@ describe('None Data Source configuration', () => { describe('adding none data source from imported api', () => { test('imported api can add NoneDataSource from id', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, }); importedApi.addNoneDataSource('none'); @@ -89,7 +89,7 @@ describe('adding none data source from imported api', () => { test('imported api can add NoneDataSource from attributes', () => { // WHEN - const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + const importedApi = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'importedApi', { graphqlApiId: api.apiId, graphqlApiArn: api.arn, }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts index 02f0d9b43ff57..a60a5242f6fe7 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts @@ -4,11 +4,11 @@ import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'api', { + api = new appsync.GraphQLApi(stack, 'api', { name: 'api', }); }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts index 312d7a3784b98..64fddd55bc3d3 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-scalar-type.test.ts @@ -4,11 +4,11 @@ import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'api', { + api = new appsync.GraphQLApi(stack, 'api', { name: 'api', }); }); 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 95fe1ac7c500a..f60d63f1dec5c 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-schema.test.ts @@ -36,7 +36,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` produces empty schema definition', () => { // WHEN - new appsync.GraphqlApi(stack, 'API', { + new appsync.GraphQLApi(stack, 'API', { name: 'demo', }); @@ -48,7 +48,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` generates correct schema with addToSchema', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', }); api.addType(type); @@ -63,7 +63,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for api to addQuery', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', }); api.addQuery('test', new appsync.ResolvableField({ @@ -79,7 +79,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for schema to addQuery', () => { // WHEN const schema = new appsync.Schema(); - new appsync.GraphqlApi(stack, 'API', { + new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema, }); @@ -95,7 +95,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for api to addMutation', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', }); api.addMutation('test', new appsync.ResolvableField({ @@ -111,7 +111,7 @@ describe('basic testing schema definition mode `code`', () => { test('definition mode `code` allows for schema to addMutation', () => { // WHEN const schema = new appsync.Schema(); - new appsync.GraphqlApi(stack, 'API', { + new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema, }); @@ -130,7 +130,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` produces correct output', () => { // WHEN - new appsync.GraphqlApi(stack, 'API', { + new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -143,7 +143,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addType for object is called', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -158,7 +158,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addType for interface is called', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -173,7 +173,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addToSchema is called', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -186,7 +186,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addQuery is called', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); @@ -199,7 +199,7 @@ describe('testing schema definition mode `file`', () => { test('definition mode `file` errors when addMutation is called', () => { // WHEN - const api = new appsync.GraphqlApi(stack, 'API', { + const api = new appsync.GraphQLApi(stack, 'API', { name: 'demo', schema: appsync.Schema.fromAsset(join(__dirname, 'appsync.test.graphql')), }); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index debdfa71ce4f0..9dc6808a672e1 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -4,10 +4,10 @@ import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; let stack: cdk.Stack; -let api: appsync.GraphqlApi; +let api: appsync.GraphQLApi; beforeEach(() => { stack = new cdk.Stack(); - api = new appsync.GraphqlApi(stack, 'api', { + api = new appsync.GraphQLApi(stack, 'api', { authorizationConfig: {}, name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), @@ -61,7 +61,7 @@ test('appsync should configure resolver as unit when pipelineConfig is empty arr test('when xray is enabled should not throw an Error', () => { // WHEN - new appsync.GraphqlApi(stack, 'api-x-ray', { + new appsync.GraphQLApi(stack, 'api-x-ray', { authorizationConfig: {}, name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), diff --git a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts index 8781f83fe117e..b36c6a1bef7b1 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.api-import.ts @@ -11,7 +11,7 @@ import * as appsync from '../lib'; * * Stack verification steps: * Install dependencies and deploy integration test. Check if data sources are - * connected to the GraphQL Api + * connected to the graphQL Api * * -- cdk deploy --app 'node integ.api-import.js' stack -- start -- * -- aws appsync list-graphql-apis -- obtain api id -- @@ -22,13 +22,13 @@ import * as appsync from '../lib'; const app = new cdk.App(); const baseStack = new cdk.Stack(app, 'baseStack'); -const baseApi = new appsync.GraphqlApi(baseStack, 'baseApi', { +const baseApi = new appsync.GraphQLApi(baseStack, 'baseApi', { name: 'baseApi', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), }); const stack = new cdk.Stack(app, 'stack'); -const api = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'Api', { +const api = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'Api', { graphqlApiId: `${baseApi.apiId}`, }); @@ -57,7 +57,7 @@ testDS.createResolver({ responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultItem(), }); -const api2 = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'api2', { +const api2 = appsync.GraphQLApi.fromGraphqlApiAttributes(stack, 'api2', { graphqlApiId: baseApi.apiId, graphqlApiArn: baseApi.arn, }); 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 185fc8ef3e729..0d4c95a10ea3f 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-iam.ts @@ -6,7 +6,7 @@ import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import { AuthorizationType, - GraphqlApi, + GraphQLApi, MappingTemplate, PrimaryKey, UserPoolDefaultAction, @@ -36,7 +36,7 @@ const userPool = new UserPool(stack, 'Pool', { userPoolName: 'myPool', }); -const api = new GraphqlApi(stack, 'Api', { +const api = new GraphQLApi(stack, 'Api', { name: 'Integ_Test_IAM', schema: Schema.fromAsset(join(__dirname, 'integ.graphql-iam.graphql')), authorizationConfig: { @@ -98,14 +98,14 @@ new Function(stack, 'testQuery', { code: Code.fromAsset('verify'), handler: 'iam-query.handler', runtime: Runtime.NODEJS_12_X, - environment: { APPSYNC_ENDPOINT: api.graphqlUrl }, + environment: { APPSYNC_ENDPOINT: api.graphQlUrl }, role: lambdaIAM, }); new Function(stack, 'testFail', { code: Code.fromAsset('verify'), handler: 'iam-query.handler', runtime: Runtime.NODEJS_12_X, - environment: { APPSYNC_ENDPOINT: api.graphqlUrl }, + environment: { APPSYNC_ENDPOINT: api.graphQlUrl }, }); app.synth(); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 8cb3daafab11a..8bde313c6f724 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -29,7 +29,7 @@ const node = schema.addType(new appsync.InterfaceType('Node', { }, })); -const api = new appsync.GraphqlApi(stack, 'code-first-api', { +const api = new appsync.GraphQLApi(stack, 'code-first-api', { name: 'api', schema: schema, }); diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 1de80995c90a0..9882fead1cf12 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -4,7 +4,7 @@ import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb'; import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; import { AuthorizationType, - GraphqlApi, + GraphQLApi, KeyCondition, MappingTemplate, PrimaryKey, @@ -33,7 +33,7 @@ const userPool = new UserPool(stack, 'Pool', { userPoolName: 'myPool', }); -const api = new GraphqlApi(stack, 'Api', { +const api = new GraphQLApi(stack, 'Api', { name: 'demoapi', schema: Schema.fromAsset(join(__dirname, 'integ.graphql.graphql')), authorizationConfig: { From b9d0d6344d9ba04f2af893714e3c7fc43d975fc3 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Mon, 31 Aug 2020 18:04:33 -0700 Subject: [PATCH 77/78] simplify public api for cognito and add check for modes in directives --- .../@aws-cdk/aws-appsync/lib/graphqlapi.ts | 7 ++ packages/@aws-cdk/aws-appsync/lib/private.ts | 9 ++- .../@aws-cdk/aws-appsync/lib/schema-base.ts | 45 ++++++++---- .../@aws-cdk/aws-appsync/lib/schema-field.ts | 7 +- .../aws-appsync/lib/schema-intermediate.ts | 23 ++---- packages/@aws-cdk/aws-appsync/lib/schema.ts | 12 +++- .../test/appsync-directives.test.ts | 71 +++++++++++++++---- .../test/appsync-interface-type.test.ts | 8 +-- .../test/appsync-object-type.test.ts | 8 +-- .../aws-appsync/test/integ.graphql-schema.ts | 2 +- 10 files changed, 133 insertions(+), 59 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 4251336f017ea..1c6807173a6be 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -376,6 +376,11 @@ export class GraphQLApi extends GraphqlApiBase { */ public readonly schema: Schema; + /** + * The Authorization Types for this GraphQL Api + */ + public readonly modes: AuthorizationType[]; + /** * the configured API key, if present * @@ -395,6 +400,8 @@ export class GraphQLApi extends GraphqlApiBase { const additionalModes = props.authorizationConfig?.additionalAuthorizationModes ?? []; const modes = [defaultMode, ...additionalModes]; + this.modes = modes.map((mode) => mode.authorizationType ); + this.validateAuthorizationProps(modes); this.api = new CfnGraphQLApi(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-appsync/lib/private.ts b/packages/@aws-cdk/aws-appsync/lib/private.ts index 25e99fa93b753..edb5db59c387d 100644 --- a/packages/@aws-cdk/aws-appsync/lib/private.ts +++ b/packages/@aws-cdk/aws-appsync/lib/private.ts @@ -1,3 +1,4 @@ +import { AuthorizationType } from './graphqlapi'; import { Directive } from './schema-base'; import { InterfaceType } from './schema-intermediate'; @@ -47,6 +48,10 @@ export interface SchemaAdditionOptions { * the fields to reduce onto the addition */ readonly fields: string[]; + /** + * the authorization modes for this graphql type + */ + readonly modes?: AuthorizationType[]; } /** @@ -203,8 +208,8 @@ function generateInterfaces(interfaceTypes?: InterfaceType[]): string { * @param directives the directives of a given type * @param delimiter the separator betweeen directives (by default we will add a space) */ -function generateDirectives(directives?: Directive[], delimiter?: string): string { +function generateDirectives(directives?: Directive[], delimiter?: string, modes?: AuthorizationType[]): string { if (!directives || directives.length === 0) return ''; return directives.reduce((acc, directive) => - `${acc}${directive.statement}${delimiter ?? ' '}`, ' ').slice(0, -1); + `${acc}${directive.toString(modes)}${delimiter ?? ' '}`, ' ').slice(0, -1); } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index 09946d271ea0d..773b49e1c2a43 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -1,3 +1,4 @@ +import { AuthorizationType } from './graphqlapi'; import { Resolver } from './resolver'; import { ResolvableFieldOptions, BaseTypeOptions, GraphqlType } from './schema-field'; import { InterfaceType } from './schema-intermediate'; @@ -64,8 +65,10 @@ export interface IField { /** * Generate the directives for this field + * + * @param modes the authorization modes of the graphql api */ - directivesToString(): string + directivesToString(modes?: AuthorizationType[]): string } /** @@ -122,8 +125,10 @@ export interface IIntermediateType { /** * Generate the string of this object type + * + * @param modes the authorization modes for the graphql api */ - toString(): string; + toString(modes?: AuthorizationType[]): string; /** * Add a field to this Intermediate Type @@ -146,39 +151,36 @@ export class Directive { * Add the @aws_iam directive */ public static iam(): Directive { - return new Directive('@aws_iam'); + return new Directive('@aws_iam', AuthorizationType.IAM); } /** * Add the @aws_oidc directive */ public static oidc(): Directive { - return new Directive('@aws_oidc'); + return new Directive('@aws_oidc', AuthorizationType.OIDC); } /** * Add the @aws_api_key directive */ public static apiKey(): Directive { - return new Directive('@aws_api_key'); + return new Directive('@aws_api_key', AuthorizationType.API_KEY); } /** * Add the @aws_auth or @aws_cognito_user_pools directive * * @param groups the groups to allow access to - * @param additional is cognito an additional authorization? - * @default false */ - public static cognito(groups: string[], additional?: boolean): Directive { + public static cognito(...groups: string[]): Directive { if (groups.length === 0) { throw new Error('Groups parameter must not be empty.'); } - const prefix = additional ? '@aws_cognito_user_pools' : '@aws_auth'; const stringify = (array: string[]): string => { return array.reduce((acc, element) => `${acc}"${element}", `, '[').slice(0, -2) + ']'; }; - return new Directive(`${prefix}(cognito_groups: ${stringify(groups)})`); + return new Directive(`@aws_auth(cognito_groups: ${stringify(groups)})`, AuthorizationType.USER_POOL); } /** @@ -193,9 +195,28 @@ export class Directive { /** * the directive statement */ - public readonly statement: string; + private statement: string; + + private readonly mode?: AuthorizationType; + + private constructor(statement: string, mode?: AuthorizationType) { + this.statement = statement; + this.mode = mode; + } - private constructor(statement: string) { this.statement = statement; } + /** + * Generate the directive statement + * @param modes the authorization modes of the graphql api + */ + public toString(modes?: AuthorizationType[]): string { + if (modes && this.mode && !modes.some((mode) => mode === this.mode)) { + throw new Error(`No Authoration Type ${this.mode} declared in GraphQL Api.`); + } + if (this.mode === AuthorizationType.USER_POOL && modes && modes.length > 1) { + this.statement = this.statement.replace('@aws_auth', '@aws_cognito_user_pools'); + } + return this.statement; + } } /** diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts index 41fdbd79dac22..4b5f8c0f3af62 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-field.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -1,4 +1,5 @@ import { BaseDataSource } from './data-source'; +import { AuthorizationType } from './graphqlapi'; import { MappingTemplate } from './mapping-template'; import { Type, IField, IIntermediateType, Directive } from './schema-base'; @@ -325,7 +326,7 @@ export class GraphqlType implements IField { /** * Generate the directives for this field */ - public directivesToString(): string { + public directivesToString(_modes?: AuthorizationType[]): string { return ''; } } @@ -395,10 +396,10 @@ export class Field extends GraphqlType implements IField { /** * Generate the directives for this field */ - public directivesToString(): string { + public directivesToString(modes?: AuthorizationType[]): string { if (!this.fieldOptions || !this.fieldOptions.directives) { return ''; } return this.fieldOptions.directives.reduce((acc, directive) => - `${acc}${directive.statement} `, '\n ').slice(0, -1); + `${acc}${directive.toString(modes)} `, '\n ').slice(0, -1); } } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts index cb15924d8d4aa..81ce21499bea3 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -1,3 +1,4 @@ +import { AuthorizationType } from './graphqlapi'; import { shapeAddition } from './private'; import { Resolver } from './resolver'; import { Directive, IField, IIntermediateType } from './schema-base'; @@ -73,15 +74,16 @@ export class InterfaceType implements IIntermediateType { /** * Generate the string of this object type */ - public toString(): string { + public toString(modes?: AuthorizationType[]): string { return shapeAddition({ prefix: 'interface', name: this.name, directives: this.directives, fields: Object.keys(this.definition).map((key) => { const field = this.definition[key]; - return `${key}${field.argsToString()}: ${field.toString()}${field.directivesToString()}`; + return `${key}${field.argsToString()}: ${field.toString()}${field.directivesToString(modes)}`; }), + modes, }); } @@ -94,18 +96,6 @@ export class InterfaceType implements IIntermediateType { public addField(fieldName: string, field: IField): void { this.definition[fieldName] = field; } - - /** - * Utility function to generate directives - * - * @param delimiter the separator betweeen directives - * @default - ' ' - */ - protected generateDirectives(delimiter?: string): string { - if (!this.directives) { return ''; } - return this.directives.reduce((acc, directive) => - `${acc}${directive.statement}${delimiter ?? ' '}`, ''); - } } /** @@ -175,7 +165,7 @@ export class ObjectType extends InterfaceType implements IIntermediateType { /** * Generate the string of this object type */ - public toString(): string { + public toString(modes?: AuthorizationType[]): string { return shapeAddition({ prefix: 'type', name: this.name, @@ -183,8 +173,9 @@ export class ObjectType extends InterfaceType implements IIntermediateType { directives: this.directives, fields: Object.keys(this.definition).map((key) => { const field = this.definition[key]; - return `${key}${field.argsToString()}: ${field.toString()}${field.directivesToString()}`; + return `${key}${field.argsToString()}: ${field.toString()}${field.directivesToString(modes)}`; }), + modes, }); } diff --git a/packages/@aws-cdk/aws-appsync/lib/schema.ts b/packages/@aws-cdk/aws-appsync/lib/schema.ts index f7751f38a3182..fc321459be2ee 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema.ts @@ -56,6 +56,8 @@ export class Schema { private mode: SchemaMode; + private types: IIntermediateType[]; + public constructor(options?: SchemaOptions) { if (options?.filePath) { this.mode = SchemaMode.FILE; @@ -64,6 +66,7 @@ export class Schema { this.mode = SchemaMode.CODE; this.definition = ''; } + this.types = []; } /** @@ -76,7 +79,12 @@ export class Schema { if (!this.schema) { this.schema = new CfnGraphQLSchema(api, 'Schema', { apiId: api.apiId, - definition: Lazy.stringValue({ produce: () => `${this.declareSchema()}${this.definition}` }), + definition: this.mode === SchemaMode.CODE ? + Lazy.stringValue({ + produce: () => this.types.reduce((acc, type) => { return `${acc}${type.toString(api.modes)}\n`; }, + `${this.declareSchema()}${this.definition}`), + }) + : this.definition, }); } return this.schema; @@ -157,7 +165,7 @@ export class Schema { if (this.mode !== SchemaMode.CODE) { throw new Error('API cannot add type because schema definition mode is not configured as CODE.'); } - this.addToSchema(Lazy.stringValue({ produce: () => type.toString() })); + this.types.push(type); return type; } diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts index 8871801f765c8..31c433a7d11d7 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts @@ -1,13 +1,14 @@ import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; +import * as cognito from '@aws-cdk/aws-cognito'; import * as appsync from '../lib'; import * as t from './scalar-type-defintions'; const iam = [appsync.Directive.iam()]; const apiKey = [appsync.Directive.apiKey()]; const oidc = [appsync.Directive.oidc()]; -const cognito_default = [appsync.Directive.cognito(['test', 'test2'])]; -const cognito_additional = [appsync.Directive.cognito(['test', 'test2'], true)]; +const cognito_default = [appsync.Directive.cognito('test', 'test2')]; +const cognito_additional = [appsync.Directive.cognito('test', 'test2')]; const custom = [appsync.Directive.custom('custom')]; const generateField = (directives: appsync.Directive[]): appsync.Field => { @@ -25,12 +26,52 @@ const generateRField = (directives: appsync.Directive[]): appsync.ResolvableFiel }; let stack: cdk.Stack; -let api: appsync.GraphQLApi; + +let api_apiKey: appsync.GraphQLApi, api_iam: appsync.GraphQLApi, api_oidc: appsync.GraphQLApi, + api_auth: appsync.GraphQLApi, api_cognito: appsync.GraphQLApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); - api = new appsync.GraphQLApi(stack, 'api', { + const userPool = new cognito.UserPool(stack, 'userpool'); + api_apiKey = new appsync.GraphQLApi(stack, 'api_apiKey', { + name: 'api', + }); + api_iam = new appsync.GraphQLApi(stack, 'api_iam', { name: 'api', + authorizationConfig: { + defaultAuthorization: { + authorizationType: appsync.AuthorizationType.IAM, + }, + }, + }); + api_oidc = new appsync.GraphQLApi(stack, 'api_oidc', { + name: 'api', + authorizationConfig: { + defaultAuthorization: { + authorizationType: appsync.AuthorizationType.OIDC, + openIdConnectConfig: { oidcProvider: 'test' }, + }, + }, + }); + api_auth = new appsync.GraphQLApi(stack, 'api_cognito_default', { + name: 'api', + authorizationConfig: { + defaultAuthorization: { + authorizationType: appsync.AuthorizationType.USER_POOL, + userPoolConfig: { userPool }, + }, + }, + }); + api_cognito = new appsync.GraphQLApi(stack, 'api_cognito_additional', { + name: 'api', + authorizationConfig: { + additionalAuthorizationModes: [ + { + authorizationType: appsync.AuthorizationType.USER_POOL, + userPoolConfig: { userPool }, + }, + ], + }, }); }); @@ -65,35 +106,35 @@ const testInterfaceType = (IApi: appsync.GraphQLApi, directives: appsync.Directi }; describe('Basic Testing of Directives for Code-First', () => { - test('Iam directive configures in Object Type', () => { testObjectType(api, iam, '@aws_iam'); }); + test('Iam directive configures in Object Type', () => { testObjectType(api_iam, iam, '@aws_iam'); }); - test('Iam directive configures in Interface Type', () => { testInterfaceType(api, iam, '@aws_iam'); }); + test('Iam directive configures in Interface Type', () => { testInterfaceType(api_iam, iam, '@aws_iam'); }); - test('Api Key directive configures in Object Type', () => { testObjectType(api, apiKey, '@aws_api_key'); }); + test('Api Key directive configures in Object Type', () => { testObjectType(api_apiKey, apiKey, '@aws_api_key'); }); - test('Api Key directive configures in Interface Type', () => { testInterfaceType(api, apiKey, '@aws_api_key'); }); + test('Api Key directive configures in Interface Type', () => { testInterfaceType(api_apiKey, apiKey, '@aws_api_key'); }); - test('OIDC directive configures in Object Type', () => { testObjectType(api, oidc, '@aws_oidc'); }); + test('OIDC directive configures in Object Type', () => { testObjectType(api_oidc, oidc, '@aws_oidc'); }); - test('OIDC directive configures in Interface Type', () => { testInterfaceType(api, oidc, '@aws_oidc'); }); + test('OIDC directive configures in Interface Type', () => { testInterfaceType(api_oidc, oidc, '@aws_oidc'); }); test('Cognito as default directive configures in Object Type', () => { - testObjectType(api, cognito_default, '@aws_auth(cognito_groups: ["test", "test2"])'); + testObjectType(api_auth, cognito_default, '@aws_auth(cognito_groups: ["test", "test2"])'); }); test('Cognito as default directive configures in Interface Type', () => { - testInterfaceType(api, cognito_default, '@aws_auth(cognito_groups: ["test", "test2"])'); + testInterfaceType(api_auth, cognito_default, '@aws_auth(cognito_groups: ["test", "test2"])'); }); test('Cognito as additional directive configures in Object Type', () => { - testObjectType(api, cognito_additional, '@aws_cognito_user_pools(cognito_groups: ["test", "test2"])'); + testObjectType(api_cognito, cognito_additional, '@aws_cognito_user_pools(cognito_groups: ["test", "test2"])'); }); test('Custom directive configures in Object Type', () => { - testObjectType(api, custom, 'custom'); + testObjectType(api_cognito, custom, 'custom'); }); test('Custom directive configures in Interface Type', () => { - testInterfaceType(api, custom, 'custom'); + testInterfaceType(api_cognito, custom, 'custom'); }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts index 5b2c8393575bb..706f19bc18cda 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-interface-type.test.ts @@ -92,11 +92,11 @@ describe('testing InterfaceType properties', () => { }); test.addField('resolve', new appsync.Field({ returnType: t.string, - directives: [appsync.Directive.iam()], + directives: [appsync.Directive.apiKey()], })); api.addType(test); - const out = 'interface Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; + const out = 'interface Test {\n test: String\n resolve: String\n @aws_api_key\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { @@ -113,12 +113,12 @@ describe('testing InterfaceType properties', () => { }); test.addField('resolve', new appsync.ResolvableField({ returnType: t.string, - directives: [appsync.Directive.iam()], + directives: [appsync.Directive.apiKey()], dataSource: api.addNoneDataSource('none'), })); api.addType(test); - const out = 'interface Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; + const out = 'interface Test {\n test: String\n resolve: String\n @aws_api_key\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts index c7b0d5c6efe35..91c09e7d82281 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-object-type.test.ts @@ -198,11 +198,11 @@ describe('testing Object Type properties', () => { }); test.addField('resolve', new appsync.Field({ returnType: t.string, - directives: [appsync.Directive.iam()], + directives: [appsync.Directive.apiKey()], })); api.addType(test); - const out = 'type Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; + const out = 'type Test {\n test: String\n resolve: String\n @aws_api_key\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { @@ -219,12 +219,12 @@ describe('testing Object Type properties', () => { }); test.addField('resolve', new appsync.ResolvableField({ returnType: t.string, - directives: [appsync.Directive.iam()], + directives: [appsync.Directive.apiKey()], dataSource: api.addNoneDataSource('none'), })); api.addType(test); - const out = 'type Test {\n test: String\n resolve: String\n @aws_iam\n}\n'; + const out = 'type Test {\n test: String\n resolve: String\n @aws_api_key\n}\n'; // THEN expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLSchema', { diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts index 6bcfb58ea8ad6..77e2b00f1f66a 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql-schema.ts @@ -46,7 +46,7 @@ const table = new db.Table(stack, 'table', { const tableDS = api.addDynamoDbDataSource('planets', table); const planet = ObjectType.planet; -schema.addToSchema(planet.toString()); +schema.addType(planet); api.addType(new appsync.ObjectType('Species', { interfaceTypes: [node], From f641d56051a86b77f905402d41826e01891b7a73 Mon Sep 17 00:00:00 2001 From: Bryan Pan Date: Tue, 1 Sep 2020 16:55:13 -0700 Subject: [PATCH 78/78] address comments --- packages/@aws-cdk/aws-appsync/README.md | 25 +++--------- packages/@aws-cdk/aws-appsync/lib/private.ts | 38 +++++++++++++++---- .../@aws-cdk/aws-appsync/lib/schema-base.ts | 5 ++- .../test/appsync-directives.test.ts | 18 ++++----- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 11615474b5b70..a771fb43243fa 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -370,16 +370,15 @@ is `String!` and is applied to both the names `id` and `version`. #### Directives `Directives` are attached to a field or type and affect the execution of queries, -mutations, and types. With AppSync, we use `Directives` to signal authentication. +mutations, and types. With AppSync, we use `Directives` to configure authorization. CDK provides static functions to add directives to your Schema. - `Directive.iam()` sets a type or field's authorization to be validated through `Iam` - `Directive.apiKey()` sets a type or field's authorization to be validated through a `Api Key` - `Directive.oidc()` sets a type or field's authorization to be validated through `OpenID Connect` -- `Directive.cognito(groups: string[], isAdditional: boolean)` sets a type or field's - authorization to be validated through `Cognito User Pools` - - `groups` the name of the cognito groups to give access to - - `isAdditional` if using cognito authorization as an additional mode, set this to be true +- `Directive.cognito(...groups: string[])` sets a type or field's authorization to be validated +through `Cognito User Pools` + - `groups` the name of the cognito groups to give access To learn more about authorization and directives, read these docs [here](https://docs.aws.amazon.com/appsync/latest/devguide/security.html). @@ -564,21 +563,7 @@ You can create Object Types in three ways: }, }); ``` - > This method allows for reusability and modularity, ideal for reducing code duplication. - -3. Object Types can be created ***internally*** within the GraphQL API. - ```ts - const api = new appsync.GraphqlApi(stack, 'Api', { - name: 'demo', - }); - api.addType('Demo', { - defintion: { - id: appsync.GraphqlType.string({ isRequired: true }), - version: appsync.GraphqlType.string({ isRequired: true }), - }, - }); - ``` - > This method provides easy use and is ideal for smaller projects. + > This method allows for reusability and modularity, ideal for reducing code duplication. ##### Input Types diff --git a/packages/@aws-cdk/aws-appsync/lib/private.ts b/packages/@aws-cdk/aws-appsync/lib/private.ts index edb5db59c387d..981076113be6e 100644 --- a/packages/@aws-cdk/aws-appsync/lib/private.ts +++ b/packages/@aws-cdk/aws-appsync/lib/private.ts @@ -72,7 +72,10 @@ export interface SchemaAdditionOptions { export function shapeAddition(options: SchemaAdditionOptions): string { const typeName = (): string => { return options.name ? ` ${options.name}` : ''; }; const interfaces = generateInterfaces(options.interfaceTypes); - const directives = generateDirectives(options.directives); + const directives = generateDirectives({ + directives: options.directives, + modes: options.modes, + }); return options.fields.reduce((acc, field) => `${acc} ${field}\n`, `${options.prefix}${typeName()}${interfaces}${directives} {\n`) + '}'; } @@ -202,14 +205,33 @@ function generateInterfaces(interfaceTypes?: InterfaceType[]): string { `${acc} ${interfaceType.name},`, ' implements').slice(0, -1); } +/** + * options to generate directives + */ +interface generateDirectivesOptions { + /** + * the directives of a given type + */ + readonly directives?: Directive[]; + /** + * thee separator betweeen directives + * + * @default - a space + */ + readonly delimiter?: string; + /** + * the authorization modes + */ + readonly modes?: AuthorizationType[]; +} + /** * Utility function to generate directives - * - * @param directives the directives of a given type - * @param delimiter the separator betweeen directives (by default we will add a space) */ -function generateDirectives(directives?: Directive[], delimiter?: string, modes?: AuthorizationType[]): string { - if (!directives || directives.length === 0) return ''; - return directives.reduce((acc, directive) => - `${acc}${directive.toString(modes)}${delimiter ?? ' '}`, ' ').slice(0, -1); +function generateDirectives(options: generateDirectivesOptions): string { + if (!options.directives || options.directives.length === 0) return ''; + // reduce over all directives and get string version of the directive + // pass in the auth modes for checks to happen on compile time + return options.directives.reduce((acc, directive) => + `${acc}${directive.toString(options.modes)}${options.delimiter ?? ' '}`, ' ').slice(0, -1); } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts index 2d91f3ceb2118..628cbf80af8c3 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -198,8 +198,9 @@ export class Directive { */ public static cognito(...groups: string[]): Directive { if (groups.length === 0) { - throw new Error('Groups parameter must not be empty.'); + throw new Error(`Cognito authorization requires at least one Cognito group to be supplied. Received: ${groups.length}`); } + // this function creates the cognito groups as a string (i.e. ["group1", "group2", "group3"]) const stringify = (array: string[]): string => { return array.reduce((acc, element) => `${acc}"${element}", `, '[').slice(0, -2) + ']'; }; @@ -233,7 +234,7 @@ export class Directive { */ public toString(modes?: AuthorizationType[]): string { if (modes && this.mode && !modes.some((mode) => mode === this.mode)) { - throw new Error(`No Authoration Type ${this.mode} declared in GraphQL Api.`); + throw new Error(`No Authorization Type ${this.mode} declared in GraphQL Api.`); } if (this.mode === AuthorizationType.USER_POOL && modes && modes.length > 1) { this.statement = this.statement.replace('@aws_auth', '@aws_cognito_user_pools'); diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts index 31c433a7d11d7..3c47e0b63cf6d 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-directives.test.ts @@ -27,16 +27,16 @@ const generateRField = (directives: appsync.Directive[]): appsync.ResolvableFiel let stack: cdk.Stack; -let api_apiKey: appsync.GraphQLApi, api_iam: appsync.GraphQLApi, api_oidc: appsync.GraphQLApi, - api_auth: appsync.GraphQLApi, api_cognito: appsync.GraphQLApi; +let api_apiKey: appsync.GraphqlApi, api_iam: appsync.GraphqlApi, api_oidc: appsync.GraphqlApi, + api_auth: appsync.GraphqlApi, api_cognito: appsync.GraphqlApi; beforeEach(() => { // GIVEN stack = new cdk.Stack(); const userPool = new cognito.UserPool(stack, 'userpool'); - api_apiKey = new appsync.GraphQLApi(stack, 'api_apiKey', { + api_apiKey = new appsync.GraphqlApi(stack, 'api_apiKey', { name: 'api', }); - api_iam = new appsync.GraphQLApi(stack, 'api_iam', { + api_iam = new appsync.GraphqlApi(stack, 'api_iam', { name: 'api', authorizationConfig: { defaultAuthorization: { @@ -44,7 +44,7 @@ beforeEach(() => { }, }, }); - api_oidc = new appsync.GraphQLApi(stack, 'api_oidc', { + api_oidc = new appsync.GraphqlApi(stack, 'api_oidc', { name: 'api', authorizationConfig: { defaultAuthorization: { @@ -53,7 +53,7 @@ beforeEach(() => { }, }, }); - api_auth = new appsync.GraphQLApi(stack, 'api_cognito_default', { + api_auth = new appsync.GraphqlApi(stack, 'api_cognito_default', { name: 'api', authorizationConfig: { defaultAuthorization: { @@ -62,7 +62,7 @@ beforeEach(() => { }, }, }); - api_cognito = new appsync.GraphQLApi(stack, 'api_cognito_additional', { + api_cognito = new appsync.GraphqlApi(stack, 'api_cognito_additional', { name: 'api', authorizationConfig: { additionalAuthorizationModes: [ @@ -75,7 +75,7 @@ beforeEach(() => { }); }); -const testObjectType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[], tag: string): any => { +const testObjectType = (IApi: appsync.GraphqlApi, directives: appsync.Directive[], tag: string): any => { // WHEN IApi.addType(new appsync.ObjectType('Test', { definition: { @@ -90,7 +90,7 @@ const testObjectType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[ }); }; -const testInterfaceType = (IApi: appsync.GraphQLApi, directives: appsync.Directive[], tag: string): any => { +const testInterfaceType = (IApi: appsync.GraphqlApi, directives: appsync.Directive[], tag: string): any => { // WHEN IApi.addType(new appsync.InterfaceType('Test', { definition: {