Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,20 @@ custom:
attributeType: S
action: DeleteItem
cors: true
- dynamodb:
path: /dynamodb/{id}
method: get
tableName: { Ref: 'YourTable' }
hashKey:
keyName: id # Name of the attribute in the DynamoDB table
attributeValue: \${input.params().path.id1}_\${input.params().querystring.id2} # Custom mapping for the attribute. Use '\${}' to escape the '$' character.
attributeType: S
rangeKey:
keyName: range # Name of the attribute in the DynamoDB table
attributeValue: \${input.params().path.range1}_\${input.params().querystring.range2} # Custom mapping for the attribute. Use '\${}' to escape the '$' character.
attributeType: S
action: GetItem
cors: true

resources:
Resources:
Expand Down
6 changes: 4 additions & 2 deletions lib/apiGateway/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,15 @@ const dynamodbDefaultKeyScheme = Joi.object()
.keys({
pathParam: Joi.string(),
queryStringParam: Joi.string(),
keyName: Joi.string(),
attributeValue: Joi.string(),
attributeType: Joi.string().required()
})
.xor('pathParam', 'queryStringParam')
.xor('pathParam', 'queryStringParam', 'keyName')
.error(
customErrorBuilder(
'object.xor',
'key must contain "pathParam" or "queryStringParam" and only one'
'key must contain "pathParam" or "queryStringParam" or "keyName" and only one'
)
)

Expand Down
83 changes: 75 additions & 8 deletions lib/apiGateway/validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1829,7 +1829,7 @@ describe('#validateServiceProxies()', () => {
)
})

it('should throw error if the "hashKey" is object and missing "pathParam" or "queryStringParam" properties', () => {
it('should throw error if the "hashKey" is object and missing "pathParam", "queryStringParam" or "keyName" properties', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
Expand All @@ -1847,7 +1847,7 @@ describe('#validateServiceProxies()', () => {
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "dynamodb" fails because [child "hashKey" fails because ["hashKey" must contain at least one of [pathParam, queryStringParam]]]'
'child "dynamodb" fails because [child "hashKey" fails because ["hashKey" must contain at least one of [pathParam, queryStringParam, keyName]]]'
)
})

Expand Down Expand Up @@ -1915,7 +1915,7 @@ describe('#validateServiceProxies()', () => {
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
})

it('should not throw error if the "hashKey" is object and has both "pathParam" and "attributeType" properties', () => {
it('should not throw error if the "hashKey" is object and has both "keyName" and "attributeType" properties', () => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already a it('should not throw error if the "hashKey" is object and has both "pathParam" and "attributeType" properties', () => { on line 1876 so I repurposed this test.

serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
Expand All @@ -1925,7 +1925,8 @@ describe('#validateServiceProxies()', () => {
method: 'post',
action: 'PutItem',
hashKey: {
pathParam: 'id',
keyName: 'id',
attributeValue: '${input.params().path.id1}_${input.params().path.id2}',
attributeType: 'S'
}
}
Expand Down Expand Up @@ -1992,11 +1993,31 @@ describe('#validateServiceProxies()', () => {
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "dynamodb" fails because [child "hashKey" fails because [key must contain "pathParam" or "queryStringParam" and only one]]'
'child "dynamodb" fails because [child "hashKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
)
})

it('should throw error if the "hashKey" is a pathParam and a keyName at the same time', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
dynamodb: {
tableName: 'yourStream',
path: 'dynamodb',
method: 'put',
action: 'PutItem',
hashKey: { pathParam: 'id', keyName: 'id', attributeType: 'S' }
}
}
]
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "dynamodb" fails because [child "hashKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
)
})

it('should throw error if the "rangeKey" is object and missing "pathParam" or "queryStringParam" properties', () => {
it('should throw error if the "rangeKey" is object and missing "pathParam", "queryStringParam" or "keyName" properties', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
Expand All @@ -2018,7 +2039,7 @@ describe('#validateServiceProxies()', () => {
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "dynamodb" fails because [child "rangeKey" fails because ["rangeKey" must contain at least one of [pathParam, queryStringParam]]]'
'child "dynamodb" fails because [child "rangeKey" fails because ["rangeKey" must contain at least one of [pathParam, queryStringParam, keyName]]]'
)
})

Expand Down Expand Up @@ -2098,6 +2119,31 @@ describe('#validateServiceProxies()', () => {
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
})

it('should not throw error if the "rangeKey" is object and has both "keyName" and "attributeType" properties', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
dynamodb: {
tableName: 'yourTable',
path: 'dynamodb',
method: 'post',
action: 'PutItem',
hashKey: {
keyName: 'id',
attributeType: 'S'
},
hashKey: {
keyName: 'sort',
attributeType: 'S'
}
}
}
]
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
})

it('should throw error if the "rangeKey" is not a string or an object', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
Expand Down Expand Up @@ -2164,7 +2210,28 @@ describe('#validateServiceProxies()', () => {
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "dynamodb" fails because [child "rangeKey" fails because [key must contain "pathParam" or "queryStringParam" and only one]]'
'child "dynamodb" fails because [child "rangeKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
)
})

it('should throw error if the "rangeKey" is a pathParam and a keyName at the same time', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
dynamodb: {
tableName: 'yourStream',
path: 'dynamodb',
method: 'put',
action: 'PutItem',
hashKey: { pathParam: 'id', attributeType: 'S' },
rangeKey: { pathParam: 'id', keyName: 'id', attributeType: 'S' }
}
}
]
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "dynamodb" fails because [child "rangeKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
)
})

Expand Down
45 changes: 24 additions & 21 deletions lib/package/dynamodb/compileMethodsToDynamodb.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,39 +155,42 @@ module.exports = {
},

getDynamodbObjectHashkeyParameter(http) {
if (http.hashKey.pathParam) {
return {
key: http.hashKey.pathParam,
attributeType: http.hashKey.attributeType,
attributeValue: `$input.params().path.${http.hashKey.pathParam}`
}
}
return this.getDynamodbObjectKeyParameter(http.hashKey)
},

if (http.hashKey.queryStringParam) {
getDynamodbObjectRangekeyParameter(http) {
return this.getDynamodbObjectKeyParameter(http.rangeKey)
},

getDynamodbObjectKeyParameter(key) {
if (key.pathParam) {
return {
key: http.hashKey.queryStringParam,
attributeType: http.hashKey.attributeType,
attributeValue: `$input.params().querystring.${http.hashKey.queryStringParam}`
key: key.pathParam,
attributeType: key.attributeType,
attributeValue: `$input.params().path.${key.pathParam}`
}
}
},

getDynamodbObjectRangekeyParameter(http) {
if (http.rangeKey.pathParam) {
if (key.queryStringParam) {
return {
key: http.rangeKey.pathParam,
attributeType: http.rangeKey.attributeType,
attributeValue: `$input.params().path.${http.rangeKey.pathParam}`
key: key.queryStringParam,
attributeType: key.attributeType,
attributeValue: `$input.params().querystring.${key.queryStringParam}`
}
}

if (http.rangeKey.queryStringParam) {
if (key.keyName) {
if (!key.attributeValue) {
throw new Error('If keyName is provided, attributeValue must be provided as well')
}
return {
key: http.rangeKey.queryStringParam,
attributeType: http.rangeKey.attributeType,
attributeValue: `$input.params().querystring.${http.rangeKey.queryStringParam}`
key: key.keyName,
attributeType: key.attributeType,
attributeValue: key.attributeValue
}
}

throw new Error('No valid key type found')
},

getDynamodbResponseTemplates(http, statusType) {
Expand Down
83 changes: 83 additions & 0 deletions lib/package/dynamodb/compileMethodsToDynamodb.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,43 @@ describe('#compileMethodsToDynamodb()', () => {
)
})

it('should create corresponding resources when hashkey is given with custom options', () => {
const intRequestTemplate = {
'Fn::Sub': [
'{"TableName": "${TableName}","Key":{"${HashKey}": {"${HashAttributeType}": "${HashAttributeValue}"}}}',
{
TableName: {
Ref: 'MyTable'
},
HashKey: 'id',
HashAttributeType: 'S',
HashAttributeValue: '${input.params().path.id1}_${input.params().querystring.id2}'
}
]
}
const intResponseTemplate =
'#set($item = $input.path(\'$.Item\')){#foreach($key in $item.keySet())#set ($value = $item.get($key))#foreach( $type in $value.keySet())"$key":"$value.get($type)"#if($foreach.hasNext()),#end#end#if($foreach.hasNext()),#end#end}'
testGetItem(
{
hashKey: {
keyName: 'id',
attributeValue: '${input.params().path.id1}_${input.params().querystring.id2}',
attributeType: 'S'
},
path: '/dynamodb',
action: 'GetItem'
},
{
'application/json': intRequestTemplate,
'application/x-www-form-urlencoded': intRequestTemplate
},
{
'application/json': intResponseTemplate,
'application/x-www-form-urlencoded': intResponseTemplate
}
)
})

it('should create corresponding resources when rangekey is given with a path parameter', () => {
const intRequestTemplate = {
'Fn::Sub': [
Expand Down Expand Up @@ -568,6 +605,52 @@ describe('#compileMethodsToDynamodb()', () => {
}
)
})

it('should create corresponding resources when rangekey is given with custom options', () => {
const intRequestTemplate = {
'Fn::Sub': [
'{"TableName": "${TableName}","Key":{"${HashKey}": {"${HashAttributeType}": "${HashAttributeValue}"},"${RangeKey}": {"${RangeAttributeType}": "${RangeAttributeValue}"}}}',
{
TableName: {
Ref: 'MyTable'
},
HashKey: 'id',
HashAttributeType: 'S',
HashAttributeValue: '${input.params().path.id1}_${input.params().querystring.id2}',
RangeKey: 'range',
RangeAttributeType: 'S',
RangeAttributeValue:
'${input.params().path.range1}_${input.params().querystring.range2}'
}
]
}
const intResponseTemplate =
'#set($item = $input.path(\'$.Item\')){#foreach($key in $item.keySet())#set ($value = $item.get($key))#foreach( $type in $value.keySet())"$key":"$value.get($type)"#if($foreach.hasNext()),#end#end#if($foreach.hasNext()),#end#end}'
testGetItem(
{
hashKey: {
keyName: 'id',
attributeValue: '${input.params().path.id1}_${input.params().querystring.id2}',
attributeType: 'S'
},
rangeKey: {
keyName: 'range',
attributeValue: '${input.params().path.range1}_${input.params().querystring.range2}',
attributeType: 'S'
},
path: '/dynamodb/{id}',
action: 'GetItem'
},
{
'application/json': intRequestTemplate,
'application/x-www-form-urlencoded': intRequestTemplate
},
{
'application/json': intResponseTemplate,
'application/x-www-form-urlencoded': intResponseTemplate
}
)
})
})

describe('#delete method', () => {
Expand Down