Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow generation of custom values for hash and range key #202

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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