Skip to content

Commit 28fd9d1

Browse files
committed
Allow generation of custom values for hash and range key
1 parent df81b3d commit 28fd9d1

File tree

5 files changed

+199
-30
lines changed

5 files changed

+199
-30
lines changed

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,20 @@ custom:
551551
attributeType: S
552552
action: DeleteItem
553553
cors: true
554+
- dynamodb:
555+
path: /dynamodb/{id}
556+
method: get
557+
tableName: { Ref: 'YourTable' }
558+
hashKey:
559+
keyName: id # Name of the attribute in the DynamoDB table
560+
attributeValue: \${input.params().path.id1}_\${input.params().querystring.id2} # Custom mapping for the attribute. Use '\${}' to escape the '$' character.
561+
attributeType: S
562+
rangeKey:
563+
keyName: range # Name of the attribute in the DynamoDB table
564+
attributeValue: \${input.params().path.range1}_\${input.params().querystring.range2} # Custom mapping for the attribute. Use '\${}' to escape the '$' character.
565+
attributeType: S
566+
action: GetItem
567+
cors: true
554568
555569
resources:
556570
Resources:

lib/apiGateway/schema.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,15 @@ const dynamodbDefaultKeyScheme = Joi.object()
153153
.keys({
154154
pathParam: Joi.string(),
155155
queryStringParam: Joi.string(),
156+
keyName: Joi.string(),
157+
attributeValue: Joi.string(),
156158
attributeType: Joi.string().required()
157159
})
158-
.xor('pathParam', 'queryStringParam')
160+
.xor('pathParam', 'queryStringParam', 'keyName')
159161
.error(
160162
customErrorBuilder(
161163
'object.xor',
162-
'key must contain "pathParam" or "queryStringParam" and only one'
164+
'key must contain "pathParam" or "queryStringParam" or "keyName" and only one'
163165
)
164166
)
165167

lib/apiGateway/validate.test.js

+74-7
Original file line numberDiff line numberDiff line change
@@ -1829,7 +1829,7 @@ describe('#validateServiceProxies()', () => {
18291829
)
18301830
})
18311831

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

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

@@ -1915,7 +1915,7 @@ describe('#validateServiceProxies()', () => {
19151915
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
19161916
})
19171917

1918-
it('should not throw error if the "hashKey" is object and has both "pathParam" and "attributeType" properties', () => {
1918+
it('should not throw error if the "hashKey" is object and has both "keyName" and "attributeType" properties', () => {
19191919
serverlessApigatewayServiceProxy.serverless.service.custom = {
19201920
apiGatewayServiceProxies: [
19211921
{
@@ -1925,7 +1925,8 @@ describe('#validateServiceProxies()', () => {
19251925
method: 'post',
19261926
action: 'PutItem',
19271927
hashKey: {
1928-
pathParam: 'id',
1928+
keyName: 'id',
1929+
attributeValue: '${input.params().path.id1}_${input.params().path.id2}',
19291930
attributeType: 'S'
19301931
}
19311932
}
@@ -1992,7 +1993,27 @@ describe('#validateServiceProxies()', () => {
19921993
}
19931994

19941995
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
1995-
'child "dynamodb" fails because [child "hashKey" fails because [key must contain "pathParam" or "queryStringParam" and only one]]'
1996+
'child "dynamodb" fails because [child "hashKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
1997+
)
1998+
})
1999+
2000+
it('should throw error if the "hashKey" is a pathParam and a keyName at the same time', () => {
2001+
serverlessApigatewayServiceProxy.serverless.service.custom = {
2002+
apiGatewayServiceProxies: [
2003+
{
2004+
dynamodb: {
2005+
tableName: 'yourStream',
2006+
path: 'dynamodb',
2007+
method: 'put',
2008+
action: 'PutItem',
2009+
hashKey: { pathParam: 'id', keyName: 'id', attributeType: 'S' }
2010+
}
2011+
}
2012+
]
2013+
}
2014+
2015+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
2016+
'child "dynamodb" fails because [child "hashKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
19962017
)
19972018
})
19982019

@@ -2018,7 +2039,7 @@ describe('#validateServiceProxies()', () => {
20182039
}
20192040

20202041
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
2021-
'child "dynamodb" fails because [child "rangeKey" fails because ["rangeKey" must contain at least one of [pathParam, queryStringParam]]]'
2042+
'child "dynamodb" fails because [child "rangeKey" fails because ["rangeKey" must contain at least one of [pathParam, queryStringParam, keyName]]]'
20222043
)
20232044
})
20242045

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

2122+
it('should not throw error if the "rangeKey" is object and has both "keyName" and "attributeType" properties', () => {
2123+
serverlessApigatewayServiceProxy.serverless.service.custom = {
2124+
apiGatewayServiceProxies: [
2125+
{
2126+
dynamodb: {
2127+
tableName: 'yourTable',
2128+
path: 'dynamodb',
2129+
method: 'post',
2130+
action: 'PutItem',
2131+
hashKey: {
2132+
keyName: 'id',
2133+
attributeType: 'S'
2134+
},
2135+
hashKey: {
2136+
keyName: 'sort',
2137+
attributeType: 'S'
2138+
}
2139+
}
2140+
}
2141+
]
2142+
}
2143+
2144+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
2145+
})
2146+
21012147
it('should throw error if the "rangeKey" is not a string or an object', () => {
21022148
serverlessApigatewayServiceProxy.serverless.service.custom = {
21032149
apiGatewayServiceProxies: [
@@ -2164,7 +2210,28 @@ describe('#validateServiceProxies()', () => {
21642210
}
21652211

21662212
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
2167-
'child "dynamodb" fails because [child "rangeKey" fails because [key must contain "pathParam" or "queryStringParam" and only one]]'
2213+
'child "dynamodb" fails because [child "rangeKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
2214+
)
2215+
})
2216+
2217+
it('should throw error if the "rangeKey" is a pathParam and a keyName at the same time', () => {
2218+
serverlessApigatewayServiceProxy.serverless.service.custom = {
2219+
apiGatewayServiceProxies: [
2220+
{
2221+
dynamodb: {
2222+
tableName: 'yourStream',
2223+
path: 'dynamodb',
2224+
method: 'put',
2225+
action: 'PutItem',
2226+
hashKey: { pathParam: 'id', attributeType: 'S' },
2227+
rangeKey: { pathParam: 'id', keyName: 'id', attributeType: 'S' }
2228+
}
2229+
}
2230+
]
2231+
}
2232+
2233+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
2234+
'child "dynamodb" fails because [child "rangeKey" fails because [key must contain "pathParam" or "queryStringParam" or "keyName" and only one]]'
21682235
)
21692236
})
21702237

lib/package/dynamodb/compileMethodsToDynamodb.js

+24-21
Original file line numberDiff line numberDiff line change
@@ -155,39 +155,42 @@ module.exports = {
155155
},
156156

157157
getDynamodbObjectHashkeyParameter(http) {
158-
if (http.hashKey.pathParam) {
159-
return {
160-
key: http.hashKey.pathParam,
161-
attributeType: http.hashKey.attributeType,
162-
attributeValue: `$input.params().path.${http.hashKey.pathParam}`
163-
}
164-
}
158+
return this.getDynamodbObjectKeyParameter(http.hashKey)
159+
},
165160

166-
if (http.hashKey.queryStringParam) {
161+
getDynamodbObjectRangekeyParameter(http) {
162+
return this.getDynamodbObjectKeyParameter(http.rangeKey)
163+
},
164+
165+
getDynamodbObjectKeyParameter(key) {
166+
if (key.pathParam) {
167167
return {
168-
key: http.hashKey.queryStringParam,
169-
attributeType: http.hashKey.attributeType,
170-
attributeValue: `$input.params().querystring.${http.hashKey.queryStringParam}`
168+
key: key.pathParam,
169+
attributeType: key.attributeType,
170+
attributeValue: `$input.params().path.${key.pathParam}`
171171
}
172172
}
173-
},
174173

175-
getDynamodbObjectRangekeyParameter(http) {
176-
if (http.rangeKey.pathParam) {
174+
if (key.queryStringParam) {
177175
return {
178-
key: http.rangeKey.pathParam,
179-
attributeType: http.rangeKey.attributeType,
180-
attributeValue: `$input.params().path.${http.rangeKey.pathParam}`
176+
key: key.queryStringParam,
177+
attributeType: key.attributeType,
178+
attributeValue: `$input.params().querystring.${key.queryStringParam}`
181179
}
182180
}
183181

184-
if (http.rangeKey.queryStringParam) {
182+
if (key.keyName) {
183+
if (!key.attributeValue) {
184+
throw new Error('If keyName is provided, attributeValue must be provided as well')
185+
}
185186
return {
186-
key: http.rangeKey.queryStringParam,
187-
attributeType: http.rangeKey.attributeType,
188-
attributeValue: `$input.params().querystring.${http.rangeKey.queryStringParam}`
187+
key: key.keyName,
188+
attributeType: key.attributeType,
189+
attributeValue: key.attributeValue
189190
}
190191
}
192+
193+
throw new Error('No valid key type found')
191194
},
192195

193196
getDynamodbResponseTemplates(http, statusType) {

lib/package/dynamodb/compileMethodsToDynamodb.test.js

+83
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,43 @@ describe('#compileMethodsToDynamodb()', () => {
495495
)
496496
})
497497

498+
it('should create corresponding resources when hashkey is given with custom options', () => {
499+
const intRequestTemplate = {
500+
'Fn::Sub': [
501+
'{"TableName": "${TableName}","Key":{"${HashKey}": {"${HashAttributeType}": "${HashAttributeValue}"}}}',
502+
{
503+
TableName: {
504+
Ref: 'MyTable'
505+
},
506+
HashKey: 'id',
507+
HashAttributeType: 'S',
508+
HashAttributeValue: '${input.params().path.id1}_${input.params().querystring.id2}'
509+
}
510+
]
511+
}
512+
const intResponseTemplate =
513+
'#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}'
514+
testGetItem(
515+
{
516+
hashKey: {
517+
keyName: 'id',
518+
attributeValue: '${input.params().path.id1}_${input.params().querystring.id2}',
519+
attributeType: 'S'
520+
},
521+
path: '/dynamodb',
522+
action: 'GetItem'
523+
},
524+
{
525+
'application/json': intRequestTemplate,
526+
'application/x-www-form-urlencoded': intRequestTemplate
527+
},
528+
{
529+
'application/json': intResponseTemplate,
530+
'application/x-www-form-urlencoded': intResponseTemplate
531+
}
532+
)
533+
})
534+
498535
it('should create corresponding resources when rangekey is given with a path parameter', () => {
499536
const intRequestTemplate = {
500537
'Fn::Sub': [
@@ -568,6 +605,52 @@ describe('#compileMethodsToDynamodb()', () => {
568605
}
569606
)
570607
})
608+
609+
it('should create corresponding resources when rangekey is given with custom options', () => {
610+
const intRequestTemplate = {
611+
'Fn::Sub': [
612+
'{"TableName": "${TableName}","Key":{"${HashKey}": {"${HashAttributeType}": "${HashAttributeValue}"},"${RangeKey}": {"${RangeAttributeType}": "${RangeAttributeValue}"}}}',
613+
{
614+
TableName: {
615+
Ref: 'MyTable'
616+
},
617+
HashKey: 'id',
618+
HashAttributeType: 'S',
619+
HashAttributeValue: '${input.params().path.id1}_${input.params().querystring.id2}',
620+
RangeKey: 'range',
621+
RangeAttributeType: 'S',
622+
RangeAttributeValue:
623+
'${input.params().path.range1}_${input.params().querystring.range2}'
624+
}
625+
]
626+
}
627+
const intResponseTemplate =
628+
'#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}'
629+
testGetItem(
630+
{
631+
hashKey: {
632+
keyName: 'id',
633+
attributeValue: '${input.params().path.id1}_${input.params().querystring.id2}',
634+
attributeType: 'S'
635+
},
636+
rangeKey: {
637+
keyName: 'range',
638+
attributeValue: '${input.params().path.range1}_${input.params().querystring.range2}',
639+
attributeType: 'S'
640+
},
641+
path: '/dynamodb/{id}',
642+
action: 'GetItem'
643+
},
644+
{
645+
'application/json': intRequestTemplate,
646+
'application/x-www-form-urlencoded': intRequestTemplate
647+
},
648+
{
649+
'application/json': intResponseTemplate,
650+
'application/x-www-form-urlencoded': intResponseTemplate
651+
}
652+
)
653+
})
571654
})
572655

573656
describe('#delete method', () => {

0 commit comments

Comments
 (0)