AWS API gateway response body template mapping (foreach) - amazon-web-services

I am trying to save data in S3 through firehose proxied by API gateway. I have create an API gateway endpoint that uses the AWS service integration type and PutRecord action for firehose. I have the mapping template as
{
"DeliveryStreamName": "test-stream",
"Records": [
#foreach($elem in $input.path('$.data'))
{
"Data": "$elem"
}
#if($foreach.hasNext),#end
#end
]
}
Now when I test the endpoint with below JSON
{
"data": [
{"ticker_symbol":"DemoAPIGTWY","sector":"FINANCIAL","change":-0.42,"price":50.43},{"ticker_symbol":"DemoAPIGTWY","sector":"FINANCIAL","change":-0.42,"price":50.43}
]
}
JSON gets modified and shows up as below after the transformation
{ticker_symbol=DemoAPIGTWY, sector=FINANCIAL, change=-0.42, price=50.43}
: is being converted to = which is not a valid JSON
Not sure if something is wrong in the above mapping template

The problem is, that $input.path() returns a json object and not a stringified version of the json. You can take a look at the documentation here.
The Data property expects the value to be a string and not a json object. So long story short - currently there is no built in function which can revert a json object into its stringified version. This means you need to re read the current element in the loop via $input.json(). This will return a json string representation of the element, which you then can add as Data.
Take a look at the answer here which illustrates this concept.
In your case, applying the concept described in the link above would result in a mapping like this:
{
"DeliveryStreamName": "test-stream",
"Records": [
#foreach($elem in $input.path('$.data'))
{
#set($json = $input.json("$[$foreach.index]"))
"Data":"$util.base64Encode($json)",
}
#if($foreach.hasNext),#end
#end
]
}

API Gateway considers the payload data as a text and not as a Json unless explicitly specified.
Kinesis also expects data to be in encoded format while proxying through API Gateway.
Try the following code and this should work, wondering why the for loop has been commented in the mapping template.
Assuming you are not looping through the record set, the following solution should work for you.
{
"DeliveryStreamName": "test-stream",
"Record": {
"Data": "$util.base64Encode($input.json('$.Data'))Cg=="
}
}
Thanks & Regards,
Srivignesh KN

Related

Accessing subitems in api gateway response mapping

I'm trying to do a mapping in an api gateway and I can't manage to access the children objects inside the returned json. This is my case:
When I test the endpoint directly in the api gateway I get this response:
{
"status": "FAIL",
"output": {
"errorCode": "my code",
"message": "my message"
}
}
And the api gateway integration response mapping is as follows:
#set($inputRoot = $input.path("$.output"))
$inputRoot
But I just want to return the json inside the output key, so I tried the following:
#set($inputRoot = $input.path("$.output"))
$inputRoot.output
And when I run it a get no data.
Before the transformation, the return value is
{
"output":"{\"status\":\"FAIL\",\"output\":{\"errorCode\":\"my code\",\"message\":\"my message\"}}"
}
I think that the fact that is returned as string might have something to do, but I've tried with $util.parseJson and $util.escapeJavaScript and I had no luck.
Does anyone know how can I solve this? I can't change the integration response, I have to do it through the api gateway mapping.
It should be JSON-like:
#set($inputRoot = $input.path("$.output"))
{
"output": "$inputRoot"
}

AWS API Gateway template mapping for Kinesis stream

As per aws tutorial the kinesis stream PutRecord expects base64encoded JSON string as data.
In my case I need to create an object inside the template mapping for integration request before it gets to the kinesis stream.
Code example from API gateway template mapper:
#set($inputRoot = $input.path('$'))
#set($data = {
"session": "$inputRoot.session",
"customer": "$inputRoot.customer",
"ip": "$context.identity.sourceIp"
})
{
"StreamName": "metrics-stream",
"Data": "$util.base64Encode($data)",
"PartitionKey": "$input.path('$.session')",
}
The problem: Above code doesn't work since $data is not valid JSON string. How one can convert the $data to valid JSON string? Could't find a $util to do that inside template mapper.
Thanks!

How to pass multi value query string parameter to lambda on api gateway?

I'm using aws integration api gateway with lambda and i have data mapping template.
The url with query is like
https://example.com/query?value1=val1&value1=val2&value1=val3
I'm trying to pass all those params to lambda, but have no luck - only last value is passed. Here is part of data mapping template.
"queryStringParameters": {
#foreach($queryParam in $input.params().querystring.keySet())
"$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end
#end
},
I know that there is multivaluequerystringparameters in aws proxy integration but had no luck finding them using data mapping template.
Here is test results:
Method request query string: {value1=[val1,val2,val3]}
Endpoint request body after transformations: "queryStringParameters": {"value1": "val3"}
Tried to iterate through that parameter like in VTL using #foreach but had no luck with that too
After a lot of doc search and tries, I simply write this in the mapping template (lucky...) :
"multiValueQueryStringParameters": {
#foreach($key in $method.request.multivaluequerystring.keySet())
"$key" : [
#foreach($val in $method.request.multivaluequerystring.get($key))
"$val"#if($foreach.hasNext),#end
#end
]#if($foreach.hasNext),#end
#end
},
No need to define any query parameter name in method and integration part.
So... i had two ways, send my param as
&value1=[1,2,3]
or use aws_proxy and access this value1 from event
multiValueQueryStringParameters
I choosed the last one.

AWS API Gateway - Configuring Integration Request Mapping Templates to accept String body rather JSON

I have a spring boot application with GET method like below. This method has input parameter as String that is mapped with path variable {userId}.
#GetMapping("/users/{userId}")
public String get(#PathVariable ("userId") String userId) {
return userId;
}
I created AWS lambda function and uploaded my spring boot JAR. I am able to test lambda fuction with test event after passing string example "userId1". Lambda function worked fine.
Using API gateway, created API, defined resource and GET method. URL looks like below:
/users/{userId} - GET - Integration Request
Also, followed below steps to define Mapping Templates.
Opened the Integration Request settings and then Body Mapping Templates.
Selected the option: When there are no templates defined (recommended)
Added a mapping template for: application/json
Added the following template to map the userId to the Lambda input
{
"userId": "$input.params('userId')"
}
When I test my API, it gives me below exception. I am not sure, how I should define Mapping Templates so that it accepts only String rather JSON. Because my Lambda function and corresponding implemented method accepts only String. Thanks in advance for help.
{
"errorMessage": "An error occurred during JSON parsing",
"errorType": "java.lang.RuntimeException",
"stackTrace": [],
"cause": {
"errorMessage": "com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token\n at [Source: lambdainternal.util.NativeMemoryAsInputStream#69b2283a; line: 1, column: 1]",
"errorType": "java.io.UncheckedIOException",
"stackTrace": [],
"cause": {
"errorMessage": "Can not deserialize instance of java.lang.String out of START_OBJECT token\n at [Source: lambdainternal.util.NativeMemoryAsInputStream#69b2283a; line: 1, column: 1]",
"errorType": "com.fasterxml.jackson.databind.JsonMappingException",
"stackTrace": [
"com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)",
"com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:857)",
"com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:62)",
"com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)",
"com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1511)",
"com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1102)"
]
}
}
}
I think that your problem occurred because you are mapping a JSON like:
{
"key1": "value1",
"key2": "value2",
"key3": "value3",
"key4": {
"sub_key1": "value4"
}
}
This containt a key key4 with and JSON object as value. When your are trying to deserialize this element, it won't work directly because it sees a JSON_OBJECT, not a String.
You need to create a class like the following one:
public class Name { // the name doesn't matter
#JsonProperty("sub_key1")
private String sub_key1;
// getter and setter
}
to solve your problem.
My problem is resolved with simple change. As, I mentioned in my above question, I followed below steps.
Opened the Integration Request settings and then Body Mapping Templates.
Selected the option: When there are no templates defined (recommended)
Added a mapping template for: application/json
Added the following template to map the userId to the Lambda input
{ "userId": "$input.params('userId')" }
In last step #4, I replaced below JSON format with String format.
{ "userId": "$input.params('userId')" }
With below, it worked. My API could pass String parameter properly to my lambda function which has handler method with one single input of type String.
$input.params('userId')

Getting response from AWS Lambda function to AWS Lex bot is giving error?

I have created one AWS Lex bot and I am invoking one lambda function from that bot. When testing the lambda function I am getting proper response but at bot I am getting below error:
An error has occurred: Received invalid response from Lambda: Can not
construct instance of IntentResponse: no String-argument
constructor/factory method to deserialize from String value
('2017-06-22 10:23:55.0') at [Source: "2017-06-22 10:23:55.0"; line:
1, column: 1]
Not sure, what is wrong and where I am missing. Could anyone assist me please?
The solution to above problem is that we need to make sure response returned by lambda function, to be used at AWS lex chat bot should be in below format:
{
"sessionAttributes": {
"key1": "value1",
"key2": "value2"
...
},
"dialogAction": {
"type": "ElicitIntent, ElicitSlot, ConfirmIntent, Delegate, or Close",
Full structure based on the type field. See below for details.
}
}
By this, chat bot expectd DialogAction and corresponding elements in order to process the message i.e. IntentResponse.
Reference: http://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html
no String-argument constructor/factory method to deserialize from String value
You are getting this error because you must be passing string values in the response of lambda function. You have to pass a predefined json object blueprint in the response.
Because the communication between Lex and Lambda is not simple value passing like normal functions. Amazon Lex expects output from Lambda in a particular JSON format and data is sent to Lambda in a particular JSON. The examples are here: Lambda Function Input Event and Response Format.
And just copying and pasting the blueprint won't work because in some fields you have choose between some predefined values and in some fields you have to entry valid input.
For example in,
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled or Failed",
"message": {
"contentType": "PlainText or SSML",
"content": "Thanks, your pizza has been ordered."
}
}
you have assign a value "Fulfilled" or "Failed" to field 'fulfillmentState'. And same goes for 'contentType'.