Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set object key-values in AWS API Gateway Mapping Template

I was recently acquainted to AWS's most peculiar syntax of API Gateway Mapping Templates that don't really make much sense to me.

I tried to read the documentation it was a little bit convoluted to say at least https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#util-template-reference

What I want to do is very simple. I have a request body in JSON to which I'd like to add additional field timeReceived with $context.requestTime as its value. My naive first try was #set( $bodyObj.timeReceived = $context.requestTime ) which didn't work.

I'm positive that I will get it to work but it will take time and effort to do such simple thing which IMO is silly and AWS should improve the existing documentation.

EDIT: the solution I've written below works, but I'd advocate reconsidering using API Gateway. I recently updated my app to use edge lambda and CloudFront instead which was a much better solution in every way. Some tricky configuration with the lambda not being able to use environment variables hence automating the DeliveryStreamName to the code was a bit problematic (I ended up using custom resolver with Sceptre). But still a lot better. And no need for these silly hacks =).

like image 965
TeemuK Avatar asked Jan 19 '18 11:01

TeemuK


People also ask

How do I add a mapping template to API gateway?

Navigate to the API Gateway console, choose the StoreFront API and open the GET method of the /orders resource. On the Method Execution details page, choose Integration Response. Expand the default response mapping (HTTP status 200), and expand the Mapping Templates section. Choose Add Mapping Template.

What is VTL in API gateway?

API Gateway uses the following logic to select a mapping template, in Velocity Template Language (VTL) , to map the payload from a method request to the corresponding integration request or to map the payload from an integration response to the corresponding method response.


1 Answers

EDIT: Well as of writing this thing I found out that 1# I didn't deploy my changes, embarrassingly and 2# It worked. Now I just have to stringify the JSON back with the added field.

10 minutes later: Well that was hard... So instead I found this which worked:

#set($payload = $util.parseJson($input.json('$')))
#set($body =  "{
  #foreach ($mapEntry in $payload.entrySet())
    ""$mapEntry.key"": ""$mapEntry.value"",
  #end
  ""timeReceived"": ""$context.requestTime"",
  ""x-client-ip"": ""$context.identity.sourceIp""
}")

But which expanded the JSON to multiple lines (the JSON is sent to Firehose which then stores it to S3) which is no good.

20 minutes later: My senior dev laughed at me when I told him what I was doing and commented API Gateway Mapping Templates with couple expletives.

40 minutes later: I knew what I had to do: replace all newline characters with empty strings but that proved to be harder than I thought. Also I found out that my nested values inside the object weren't stringified.

50 minutes later: Well actually I can omit the newline replacement by just having the whole foreach-loop in single line. But again I have to somehow stringify the nested values. And the requestTime is formatted in some stupid english format (no offense! :))

70 minutes later: Formatting correctly the nested values is really difficult. I found another example which worked:

#set($body =  "{
  #foreach ($mapEntry in $payload.entrySet())
    #if ($mapEntry.value.size() > 0)
      ""$mapEntry.key"": {
      #foreach($subEntry in $mapEntry.value.entrySet())
        ""$subEntry.key"": ""$subEntry.value""#if($foreach.hasNext),#end
      #end
      },
    #else
      ""$mapEntry.key"": ""$mapEntry.value"",
    #end
  #end
  ""timeReceived"": $context.requestTimeEpoch}
}")

Which translates to if you don't want new lines or extra spaces into this:

#set($body = "{#foreach ($mapEntry in $payload.entrySet())#if($mapEntry.value.size() > 0)""$mapEntry.key"": { #foreach($subEntry in $mapEntry.value.entrySet())""$subEntry.key"": ""$subEntry.value""#if($foreach.hasNext), #end#end }, #else""$mapEntry.key"": ""$mapEntry.value"", #end#end""timeReceived"": $context.requestTimeEpoch }" )

120 minutes spent both debugging and writing this: Well that was a journey. I guess I already answered my own question so hopefully now someone can avoid spending so much time debugging this as I did.

130 minutes later: Aaand now I have to solve the tragedy of adding that string into my CloudFormation template. Oh boy. (Which wasn't even hard, the hard part is redeploying the API gateway which as of writing this I had to do manually from the console)

like image 127
TeemuK Avatar answered Oct 23 '22 16:10

TeemuK