API gateway - How can I merge data in APIGateway intergration template language - amazon-web-services

I am trying to enhance data I am pushing through an APIGateway Websocket with information about the connection and user.
The integration is pushing the data into Kinesis for context. I would like to do the following where I can merge the JSON objects together at the top level.
#set($payload = $input.json('$'))
#set($data = "{""apiId"": ""$context.apiId"", ""region"": ""${AWS::Region}"", stage"": ""$context.stage"", ""connectionId"": ""$context.connectionId""}")
{
"Data": "$util.base64Encode({merge data and payload - prefer $data})",
"PartitionKey": "$context.connectionId",
"StreamName": "${EventStream}"
}
The documentation on the available language is awful, there is no reference to an underlying language to look this up. AWS does not link to this from API-gateway but can be found api-gateway-mapping-template, which suggests there is a possible way of doing this with the use of foreach statements:
#set($allParams = $input.params())
{
"params" : {
#foreach($type in $allParams.keySet())
#set($params = $allParams.get($type))
"$type" : {
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
}
}
However, my attempt fails serialisation and I can't find any context about the issue in cloudwatch.
#set($payload = $input.json('$'))
#set($data = "{""apiId"": ""$context.apiId"", ""region"": ""${AWS::Region}"", stage"": ""$context.stage"", ""connectionId"": ""$context.connectionId""}")
{
"Data": "$util.base64Encode(
{
#foreach($key in $data.keySet())
#set($value $data.get($key))
""$key"": ""$value"",
#end
#foreach($key in $data.keySet())
#set($value $data.get($key))
""$key"": ""$value""
#if($foreach.hasNext),#end
#end
}
)",
"PartitionKey": "$context.connectionId",
"StreamName": "${EventStream}"
}
the template section does link to websocket-api-selection-expressions but this seems more confusing that helpful.
Is this possible in the template? Or should I simple nest the payload into my data object?

Related

Appsync GraphQL with "None" data source trying to pass through a list of return items

I have an AppSync API that I'm using for an app. One action I'm trying to do is have a Lambda function that collects certain data fire off a GraphQL mutation, and then have a subscription on my front end collect that data when the mutation is called. This data is ephemeral and I don't want to write it to a database, so I'm trying to set up a "None" data source in AppSync just to pass this data off.
I have an AppSync GraphQL API set up with the following (simplified) schema:
type Mutation #aws_api_key
#aws_cognito_user_pools {
sendSearchResults(input: SearchResultInputHeader!): SearchResultOutputHeader
}
input SearchResultInput {
assetId: String
score: Float
}
input SearchResultInputHeader {
callId: String
results: [SearchResultInput]
}
type SearchResultOutput #aws_api_key
#aws_cognito_user_pools {
assetId: String
score: Float
}
type SearchResultOutputHeader #aws_api_key
#aws_cognito_user_pools {
callId: String
results: [SearchResultOutput]
}
and the following request / response resolver mappings:
// REQUEST::
{
"version": "2017-02-28",
"payload": {
"callId": "${context.arguments.input.callId}",
"results": "${context.arguments.input.results}"
}
}
// RESPONSE::
$util.toJson($context.result)
I am able to pass the callId String through this mutation but I am unable to get the results to pass through
// INPUT::
mutation MyMutation {
sendSearchResults(input: {resultsIn: [{assetId: "0001", score: 10}, {assetId: "0002", score: 22}], callId: "aaa-aaa-aaa"}) {
callId
resultsOut {
assetId
}
}
}
// RETURN::
{
"data": {
"sendSearchResults": {
"callId": "aaa-aaa-aaa",
"resultsOut": null
}
}
}
So I have two main questions:
How can I get the resolver/mutation to return a list of results rather than null?
Any other suggestions on passing data through an AppSync mutation and subscription? Or does this approach seem to make sense without writing to a database and just receiving a key?
Thanks!
// REQUEST::
{
"version": "2017-02-28",
"payload": {
"callId": "${context.arguments.input.callId}",
"results": "${context.arguments.input.results}"
}
}
// INPUT::
mutation MyMutation {
sendSearchResults(input: {resultsIn: [{assetId: "0001", score: 10}, {assetId: "0002", score: 22}], callId: "aaa-aaa-aaa"}) {
callId
resultsOut {
assetId
}
}
}
Here is results and resultsIn are different is two places.

How to properly set an API call in QML using XMLHttpRequest

I am building a small weather API as exercise to use QML and properly operate an API call using OpenWeather and you can see there a typical API response.
The problem I am having is that I can't get the API call to work. After setting a minimal example with some cities that you can see below, right next to the city it should appear the symbol of the weather, but it does not happen. The list of the icon can be found here. Source code of the MVE can be found here for completeness.
The error from the compiler: qrc:/main.qml:282: SyntaxError: JSON.parse: Parse error
This is what is happening
This is what is expected
Typical API JSON response can be found both here and below:
{
"coord": {
"lon": -122.08,
"lat": 37.39
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
],
"base": "stations",
"main": {
"temp": 282.55,
"feels_like": 281.86,
"temp_min": 280.37,
"temp_max": 284.26,
"pressure": 1023,
"humidity": 100
},
"visibility": 16093,
"wind": {
"speed": 1.5,
"deg": 350
},
"clouds": {
"all": 1
},
"dt": 1560350645,
"sys": {
"type": 1,
"id": 5122,
"message": 0.0139,
"country": "US",
"sunrise": 1560343627,
"sunset": 1560396563
},
"timezone": -25200,
"id": 420006353,
"name": "Mountain View",
"cod": 200
}
Below a snippet of code related to the API call:
main.qml
// Create the API getcondition to get JSON data of weather
function getCondition(location, index) {
var res
var url = "api.openweathermap.org/data/2.5/weather?id={city id}&appid={your api key}"
var doc = new XMLHttpRequest()
// parse JSON data and put code result into codeList
doc.onreadystatechange = function() {
if(doc.readyState === XMLHttpRequest.DONE) {
res = doc.responseText
// parse data
var obj = JSON.parse(res) // <-- Error Here
if(typeof(obj) == 'object') {
if(obj.hasOwnProperty('query')) {
var ch = onj.query.results.channel
var item = ch.item
codeList[index] = item.condition["code"]
}
}
}
}
doc.open('GET', url, true)
doc.send()
}
In order to solve this problem I consulted several sources, first of all : official documentation and the related function. I believe it is correctly set, but I added the reference for completeness.
Also I came across this one which explained how to simply apply XMLHttpRequest.
Also I dug more into the problem to find a solution and also consulted this one which also explained how to apply the JSON parsing function. But still something is not correct.
Thanks for pointing in the right direction for solving this problem.
Below the answer to my question. I was not reading properly the JSON file and after console logging the problem the solution is below. code was correct from beginning, only the response needed to be reviewed properly and in great detail being the JSON response a bit confusing:
function getCondition() {
var request = new XMLHttpRequest()
request.open('GET', 'http://api.openweathermap.org/data/2.5/weather?q=London&units=metric&appid=key', true);
request.onreadystatechange = function() {
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status && request.status === 200) {
console.log("response", request.responseText)
var result = JSON.parse(request.responseText)
} else {
console.log("HTTP:", request.status, request.statusText)
}
}
}
request.send()
}
Hope that helps!
In your code, your url shows this: "api.openweathermap.org/data/2.5/weather?id={city id}&appid={your api key}". You need to replace {city id} and {your api key} with real values.
You can solve it by providing an actual city ID and API key in your request URL

Update-ing dynamodb item using Appsync

In AppSync i want to update item with array or stringset like this:
mutation addmeta{
addMetaDataOnPhoto(id:"xyz", metadata:["word1", "word2",...]){
metadata
}
}
this is how my mutation type looks:
type Mutatation{
addMetaDataOnPhoto(id: String!, metadata: [String]!): Photo
}
My question is how should look resolver for this mutation.
Thanks! :)
In order to update an attribute without replacing the entire item, you should use the UpdateItem DynamoDB operation.
In your example, if you want to replace the metadata array, your request mapping template shoud look like below:
{
"version" : "2017-02-28",
"operation" : "UpdateItem",
"key" : {
"id" : { "S" : "${context.arguments.id}" }
},
"update" : {
"expression" : "SET metadata = :vals",
"expressionValues": {
":vals" : $util.dynamodb.toDynamoDBJson($ctx.args.metadata)
}
}
}
Note: $util.dynamodb.toDynamoDBJson will convert your array into a DynamoDB typedValue. For more information and utilities see the AWS AppSync util reference.

How to loop all the keys in $context.authorizer in AWS API Gateway

I need to loop all the keys in $context.authorizer.key. Is there any way to loop through it? (So that I can avoid writting each key)
"context" : {
"authorizer-principal-id" : "$context.authorizer.principalId"
}
I need something like this,
"context" : {
#foreach($key in $context)
"$key" : "$context.authorizer.$key"
#if($foreach.hasNext),#end
#end
}
From what I understand,
I think you are trying to do this
"context" : {
#foreach($paramKey in $context.authorizer.keySet())
"$paramKey" : "$context.authorizer.get($paramKey)"
#if($foreach.hasNext),#end
#end
}
Thanks!

How to pass URL query string parameters without predefining in AWS API Gateway

Is there a way not define the URL Query string parameters in both Method Request and Integration Request and pass the query string to HTTP Proxy?
For example, in case of https://{api id}-api.us-east-1.amazonaws.com/test/user?start=1&end=10
Query string
1. start
2. end
If I put URL Query string parameters in Method request and Integration request then I'm able to get both query strings. However, if I don't add them and make a request in postman then in the back end I'm not getting both query strings.
I want to get query strings without defining in Method Request and Integration Request. The reason is I don't want to type all parameters in hand if there are many.
Is there a way to do this?
I'm using HTTP Proxy and here is the template mapping that I'm using. (It's actually the Method Request passthrough drop down).
#set($allParams = $input.params())
{
"body-json" : "$input.json('$')",
"params" : {
#foreach($type in $allParams.keySet())
#set($params = $allParams.get($type))
"$type" : {
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
},
"stage-variables" : {
#foreach($key in $stageVariables.keySet())
"$key" : "$util.escapeJavaScript($stageVariables.get($key))"
#if($foreach.hasNext),#end
#end
},
"context" : {
"account-id" : "$context.identity.accountId",
"api-id" : "$context.apiId",
"api-key" : "$context.identity.apiKey",
"authorizer-principal-id" : "$context.authorizer.principalId",
"caller" : "$context.identity.caller",
"cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider",
"cognito-authentication-type" : "$context.identity.cognitoAuthenticationType",
"cognito-identity-id" : "$context.identity.cognitoIdentityId",
"cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId",
"http-method" : "$context.httpMethod",
"stage" : "$context.stage",
"source-ip" : "$context.identity.sourceIp",
"user" : "$context.identity.user",
"user-agent" : "$context.identity.userAgent",
"user-arn" : "$context.identity.userArn",
"request-id" : "$context.requestId",
"resource-id" : "$context.resourceId",
"resource-path" : "$context.resourcePath"
}
}
As noted in the AWS Forums you must define all query string parameters explicitly. There is no passthrough support for query string parameters.
You don't have to specify each parameter in the template, you can just do something like this in the mapping template to pass any parameters:
"queryParams": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
}
Also in the API Gateway UI now you can just select "Method Request passthrough" in the "Generate Template" dropdown and it will create a template for you that passes headers and parameters and other things without the need to specify specific properties that you want to pass through.