Skip to content

Commit

Permalink
Merge branch 'master' into wafv2-ipset
Browse files Browse the repository at this point in the history
  • Loading branch information
Joseph Snell authored Feb 17, 2020
2 parents c96e7d0 + 7fcbc48 commit 1e13438
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 24 deletions.
31 changes: 29 additions & 2 deletions packages/@aws-cdk/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,26 @@ input SaveCustomerInput {
name: String!
}

type Order {
customer: String!
order: String!
}

type Query {
getCustomers: [Customer]
getCustomer(id: String): Customer
}

input FirstOrderInput {
product: String!
quantity: Int!
}

type Mutation {
addCustomer(customer: SaveCustomerInput!): Customer
saveCustomer(id: String!, customer: SaveCustomerInput!): Customer
removeCustomer(id: String!): Customer
saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order
}
```

Expand Down Expand Up @@ -89,13 +100,29 @@ export class ApiStack extends Stack {
customerDS.createResolver({
typeName: 'Mutation',
fieldName: 'addCustomer',
requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer'),
requestMappingTemplate: MappingTemplate.dynamoDbPutItem(
PrimaryKey.partition('id').auto(),
Values.projecting('customer')),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
});
customerDS.createResolver({
typeName: 'Mutation',
fieldName: 'saveCustomer',
requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer', 'id'),
requestMappingTemplate: MappingTemplate.dynamoDbPutItem(
PrimaryKey.partition('id').is('id'),
Values.projecting('customer')),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
});
customerDS.createResolver({
typeName: 'Mutation',
fieldName: 'saveCustomerWithFirstOrder',
requestMappingTemplate: MappingTemplate.dynamoDbPutItem(
PrimaryKey
.partition('order').auto()
.sort('customer').is('customer.id'),
Values
.projecting('order')
.attribute('referral').is('referral')),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
});
customerDS.createResolver({
Expand Down
184 changes: 176 additions & 8 deletions packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,177 @@ export class KeyCondition {
}
}

/**
* Utility class representing the assigment of a value to an attribute.
*/
export class Assign {
constructor(private readonly attr: string, private readonly arg: string) { }

/**
* Renders the assignment as a VTL string.
*/
public renderAsAssignment(): string {
return `"${this.attr}" : $util.dynamodb.toDynamoDBJson(${this.arg})`;
}

/**
* Renders the assignment as a map element.
*/
public putInMap(map: string): string {
return `$util.qr($${map}.put("${this.attr}", "${this.arg}"))`;
}
}

/**
* Utility class to allow assigning a value or an auto-generated id
* to a partition key.
*/
export class PartitionKeyStep {
constructor(private readonly key: string) { }

/**
* Assign an auto-generated value to the partition key.
*/
public is(val: string): PartitionKey {
return new PartitionKey(new Assign(this.key, `$ctx.args.${val}`));
}

/**
* Assign an auto-generated value to the partition key.
*/
public auto(): PartitionKey {
return new PartitionKey(new Assign(this.key, '$util.autoId()'));
}
}

/**
* Utility class to allow assigning a value or an auto-generated id
* to a sort key.
*/
export class SortKeyStep {
constructor(private readonly pkey: Assign, private readonly skey: string) { }

/**
* Assign an auto-generated value to the sort key.
*/
public is(val: string): PrimaryKey {
return new PrimaryKey(this.pkey, new Assign(this.skey, `$ctx.args.${val}`));
}

/**
* Assign an auto-generated value to the sort key.
*/
public auto(): PrimaryKey {
return new PrimaryKey(this.pkey, new Assign(this.skey, '$util.autoId()'));
}
}

/**
* Specifies the assignment to the primary key. It either
* contains the full primary key or only the partition key.
*/
export class PrimaryKey {
/**
* Allows assigning a value to the partition key.
*/
public static partition(key: string): PartitionKeyStep {
return new PartitionKeyStep(key);
}

constructor(protected readonly pkey: Assign, private readonly skey?: Assign) { }

/**
* Renders the key assignment to a VTL string.
*/
public renderTemplate(): string {
const assignments = [this.pkey.renderAsAssignment()];
if (this.skey) {
assignments.push(this.skey.renderAsAssignment());
}
return `"key" : {
${assignments.join(",")}
}`;
}
}

/**
* Specifies the assignment to the partition key. It can be
* enhanced with the assignment of the sort key.
*/
export class PartitionKey extends PrimaryKey {
constructor(pkey: Assign) {
super(pkey);
}

/**
* Allows assigning a value to the sort key.
*/
public sort(key: string): SortKeyStep {
return new SortKeyStep(this.pkey, key);
}
}

/**
* Specifies the attribute value assignments.
*/
export class AttributeValues {
constructor(private readonly container: string, private readonly assignments: Assign[] = []) { }

/**
* Allows assigning a value to the specified attribute.
*/
public attribute(attr: string): AttributeValuesStep {
return new AttributeValuesStep(attr, this.container, this.assignments);
}

/**
* Renders the attribute value assingments to a VTL string.
*/
public renderTemplate(): string {
return `
#set($input = ${this.container})
${this.assignments.map(a => a.putInMap("input")).join("\n")}
"attributeValues": $util.dynamodb.toMapValuesJson($input)`;
}
}

/**
* Utility class to allow assigning a value to an attribute.
*/
export class AttributeValuesStep {
constructor(private readonly attr: string, private readonly container: string, private readonly assignments: Assign[]) { }

/**
* Assign the value to the current attribute.
*/
public is(val: string): AttributeValues {
this.assignments.push(new Assign(this.attr, val));
return new AttributeValues(this.container, this.assignments);
}
}

/**
* Factory class for attribute value assignments.
*/
export class Values {
/**
* Treats the specified object as a map of assignments, where the property
* names represent attribute names. It’s opinionated about how it represents
* some of the nested objects: e.g., it will use lists (“L”) rather than sets
* (“SS”, “NS”, “BS”). By default it projects the argument container ("$ctx.args").
*/
public static projecting(arg?: string): AttributeValues {
return new AttributeValues('$ctx.args' + (arg ? `.${arg}` : ''));
}

/**
* Allows assigning a value to the specified attribute.
*/
public static attribute(attr: string): AttributeValuesStep {
return new AttributeValues('{}').attribute(attr);
}
}

/**
* MappingTemplates for AppSync resolvers
*/
Expand Down Expand Up @@ -683,18 +854,15 @@ export abstract class MappingTemplate {
/**
* Mapping template to save a single item to a DynamoDB table
*
* @param keyName the name of the hash key field
* @param valueArg the name of the Mutation argument to use as attributes. By default it uses all arguments
* @param idArg the name of the Mutation argument to use as id value. By default it generates a new id
* @param key the assigment of Mutation values to the primary key
* @param values the assignment of Mutation values to the table attributes
*/
public static dynamoDbPutItem(keyName: string, valueArg?: string, idArg?: string): MappingTemplate {
public static dynamoDbPutItem(key: PrimaryKey, values: AttributeValues): MappingTemplate {
return this.fromString(`{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"${keyName}": $util.dynamodb.toDynamoDBJson(${idArg ? `$ctx.args.${idArg}` : '$util.autoId()'}),
},
"attributeValues" : $util.dynamodb.toMapValuesJson(${valueArg ? `$ctx.args.${valueArg}` : '$ctx.args'})
${key.renderTemplate()},
${values.renderTemplate()}
}`);
}

Expand Down
27 changes: 24 additions & 3 deletions packages/@aws-cdk/aws-appsync/test/integ.graphql.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"ApiId"
]
},
"Definition": "type Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n}"
"Definition": "type Customer {\n id: String!\n name: String!\n}\n\ninput SaveCustomerInput {\n name: String!\n}\n\ntype Order {\n customer: String!\n order: String!\n}\n\ntype Query {\n getCustomers: [Customer]\n getCustomer(id: String): Customer\n getCustomerOrdersEq(customer: String): Order\n getCustomerOrdersLt(customer: String): Order\n getCustomerOrdersLe(customer: String): Order\n getCustomerOrdersGt(customer: String): Order\n getCustomerOrdersGe(customer: String): Order\n getCustomerOrdersFilter(customer: String, order: String): Order\n getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order\n}\n\ninput FirstOrderInput {\n product: String!\n quantity: Int!\n}\n\ntype Mutation {\n addCustomer(customer: SaveCustomerInput!): Customer\n saveCustomer(id: String!, customer: SaveCustomerInput!): Customer\n removeCustomer(id: String!): Customer\n saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order\n}"
}
},
"ApiCustomerDSServiceRoleA929BCF7": {
Expand Down Expand Up @@ -161,7 +161,7 @@
"TypeName": "Mutation",
"DataSourceName": "Customer",
"Kind": "UNIT",
"RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\": $util.dynamodb.toDynamoDBJson($util.autoId()),\n },\n \"attributeValues\" : $util.dynamodb.toMapValuesJson($ctx.args.customer)\n }",
"RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($util.autoId())\n },\n \n #set($input = $ctx.args.customer)\n \n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }",
"ResponseMappingTemplate": "$util.toJson($ctx.result)"
},
"DependsOn": [
Expand All @@ -182,7 +182,28 @@
"TypeName": "Mutation",
"DataSourceName": "Customer",
"Kind": "UNIT",
"RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\": $util.dynamodb.toDynamoDBJson($ctx.args.id),\n },\n \"attributeValues\" : $util.dynamodb.toMapValuesJson($ctx.args.customer)\n }",
"RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"id\" : $util.dynamodb.toDynamoDBJson($ctx.args.id)\n },\n \n #set($input = $ctx.args.customer)\n \n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }",
"ResponseMappingTemplate": "$util.toJson($ctx.result)"
},
"DependsOn": [
"ApiCustomerDS8C23CB2D",
"ApiSchema510EECD7"
]
},
"ApiCustomerDSMutationsaveCustomerWithFirstOrderResolver8B1277A8": {
"Type": "AWS::AppSync::Resolver",
"Properties": {
"ApiId": {
"Fn::GetAtt": [
"ApiF70053CD",
"ApiId"
]
},
"FieldName": "saveCustomerWithFirstOrder",
"TypeName": "Mutation",
"DataSourceName": "Customer",
"Kind": "UNIT",
"RequestMappingTemplate": "{\n \"version\" : \"2017-02-28\",\n \"operation\" : \"PutItem\",\n \"key\" : {\n \"order\" : $util.dynamodb.toDynamoDBJson($util.autoId()),\"customer\" : $util.dynamodb.toDynamoDBJson($ctx.args.customer.id)\n },\n \n #set($input = $ctx.args.order)\n $util.qr($input.put(\"referral\", \"referral\"))\n \"attributeValues\": $util.dynamodb.toMapValuesJson($input)\n }",
"ResponseMappingTemplate": "$util.toJson($ctx.result)"
},
"DependsOn": [
Expand Down
18 changes: 15 additions & 3 deletions packages/@aws-cdk/aws-appsync/test/integ.graphql.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AttributeType, BillingMode, Table } from '@aws-cdk/aws-dynamodb';
import { App, Stack } from '@aws-cdk/core';
import { join } from 'path';
import { GraphQLApi, KeyCondition, MappingTemplate } from '../lib';
import { GraphQLApi, KeyCondition, MappingTemplate, PrimaryKey, Values } from '../lib';

const app = new App();
const stack = new Stack(app, 'aws-appsync-integ');
Expand Down Expand Up @@ -48,13 +48,25 @@ customerDS.createResolver({
customerDS.createResolver({
typeName: 'Mutation',
fieldName: 'addCustomer',
requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer'),
requestMappingTemplate: MappingTemplate.dynamoDbPutItem(PrimaryKey.partition('id').auto(), Values.projecting('customer')),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
});
customerDS.createResolver({
typeName: 'Mutation',
fieldName: 'saveCustomer',
requestMappingTemplate: MappingTemplate.dynamoDbPutItem('id', 'customer', 'id'),
requestMappingTemplate: MappingTemplate.dynamoDbPutItem(PrimaryKey.partition('id').is('id'), Values.projecting('customer')),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
});
customerDS.createResolver({
typeName: 'Mutation',
fieldName: 'saveCustomerWithFirstOrder',
requestMappingTemplate: MappingTemplate.dynamoDbPutItem(
PrimaryKey
.partition('order').auto()
.sort('customer').is('customer.id'),
Values
.projecting('order')
.attribute('referral').is('referral')),
responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
});
customerDS.createResolver({
Expand Down
6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ type Query {
getCustomerOrdersBetween(customer: String, order1: String, order2: String): Order
}

input FirstOrderInput {
product: String!
quantity: Int!
}

type Mutation {
addCustomer(customer: SaveCustomerInput!): Customer
saveCustomer(id: String!, customer: SaveCustomerInput!): Customer
removeCustomer(id: String!): Customer
saveCustomerWithFirstOrder(customer: SaveCustomerInput!, order: FirstOrderInput!, referral: String): Order
}
15 changes: 13 additions & 2 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
import { CustomResource } from '@aws-cdk/aws-cloudformation';
import { CfnCustomResource, CustomResource } from '@aws-cdk/aws-cloudformation';
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
import * as iam from '@aws-cdk/aws-iam';
import { Aws, Construct, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core';
import { Aws, CfnCondition, Construct, Fn, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core';
import { CfnTable } from './dynamodb.generated';
import { ReplicaProvider } from './replica-provider';
import { EnableScalingProps, IScalableTableAttribute } from './scalable-attribute-api';
Expand Down Expand Up @@ -1058,6 +1058,17 @@ export class Table extends TableBase {
}
});

// Deploy time check to prevent from creating a replica in the region
// where this stack is deployed. Only needed for environment agnostic
// stacks.
if (Token.isUnresolved(stack.region)) {
const createReplica = new CfnCondition(this, `StackRegionNotEquals${region}`, {
expression: Fn.conditionNot(Fn.conditionEquals(region, Aws.REGION))
});
const cfnCustomResource = currentRegion.node.defaultChild as CfnCustomResource;
cfnCustomResource.cfnOptions.condition = createReplica;
}

// We need to create/delete regions sequentially because we cannot
// have multiple table updates at the same time. The `isCompleteHandler`
// of the provider waits until the replica is an ACTIVE state.
Expand Down
Loading

0 comments on commit 1e13438

Please sign in to comment.