Curious case of AWS mapping template built-in variables
Tags: aws, appsync, apigw, cdk
Published: 2022-03-07 03:26:30

---
title: Curious case of AWS mapping template built-in variables
published: true
description: One surprising behavior of AWS mapping template built-in variables.
tags: aws, appsync, apigw, cdk
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nnr45h45wnrf4nyt75qk.jpeg
---
[Amazon API Gateway](https://aws.amazon.com/api-gateway/) is, along with [AWS Lambda](https://aws.amazon.com/lambda/), arguably, the most frequently used service in AWS serverless architectures. The service's feature spectrum, starting from rate-limiting and ending on request/response validation, is vast, making it an ideal choice for many API-driven applications.

Amongst its features, as a ["Lambda-less" enthusiast](https://dev.to/aws-builders/lambda-less-outbound-http-requests-on-aws-serverless-41mf), one is particularly interesting to me – the ability to create [mapping templates](https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html#models-mappings-mappings) that transform the payload or the response without the need for AWS Lambda.

This article will touch on something I've spent many hours debugging – the behavior of the built-in mapping template variables concerning Amazon REST API Gateway [VTL](https://velocity.apache.org/engine/devel/vtl-reference.html) and talk about my assumptions that turned out to be wrong.

> The code in this article is sourced from this [GitHub repository](https://github.com/WojciechMatuszewski/aws-mapping-template-variables).

## The architecture

I often find that often the understanding lies in the concrete. This blog post is no exception. The following architecture will serve as a reference in our discussions.

![The architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vr0636lxp9cjt64q0pez.jpeg)

The user makes a request to an API frontend with Amazon API Gateway backed by a [mock integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-mock-integration.html). The [AWS Lambda](https://aws.amazon.com/lambda/) authorizer handles authorization. The role of the mapping template is to respond with some of the Amazon API Gateway mapping template variables, mainly the ones related to authorization.

The **mapping template is crucial** since its structure dictates the response of the API. Let us look at it next.

## The code

Luckily, to showcase the behavior I'm teasing you about, we do not need to write a lot of code. A two-liner of a mapping template and a very bare-bones AWS Lambda authorizer will do.

The following is the mapping template code.

```text
{
    "authorizerContextUsernameProperty": "$context.authorizer.username",
    "wholeAuthorizerVariable": "$util.escapeJavaScript($context.authorizer)",
}
```

The mapping template declares two keys, the `authorizerContextUsernameProperty` which is read directly from the `$context.authorizer.username` and the `wholeAuthorizerVariable`. I'm escaping the value of `$context.authorizer` to avoid parsing issues.

And here is the AWS Lambda authorizer code.

```ts
export const handler: APIGatewayTokenAuthorizerHandler = async event => {
  // Assuming the user is authenticated and the token is valid

  return {
    principalId: "1",
    context: {
      username: "test-username"
    },
    policyDocument: {
      Statement: [
        {
          Action: "execute-api:Invoke",
          Effect: "Allow",
          Resource: event.methodArn
        }
      ],
      Version: "2012-10-17"
    }
  };
};
```

The authorizer is not doing much. It is only there to populate the `context` property so that we have something to work with within the mapping template.

## The surprising behavior

If I were to invoke the API, what would be the output? As a reminder, the following is our response mapping template.

```text
{
    "authorizerContextUsernameProperty": "$context.authorizer.username",
    "wholeAuthorizerVariable": "$util.escapeJavaScript($context.authorizer)",
}
```

When I first started looking into API Gateway mapping templates **I thought that the result would look similar to the following object**.

```json
{
  "authorizerContextUsernameProperty": "test-username",
  "wholeAuthorizerVariable": "{\"username\": \"test-username\", ... OTHER_PROPERTIES }"
}
```

To my surprise, **that was not the case**. Here is the response I have received.

```json
{
  "authorizerContextUsernameProperty": "test-username",
  "wholeAuthorizerVariable": ""
}
```

**Even though we clearly see that `$context.authorizer.username` is defined**, the **`$context.authorizer` somehow is "empty"**. After staring at the screen trying to come up with an answer, I've decided to check the whole `$context` object.

```diff
{
    "authorizerContextUsernameProperty": "$context.authorizer.username",
-   "wholeAuthorizerVariable": "$util.escapeJavaScript($context.authorizer)",
+   "wholeAuthorizerVariable": "$util.escapeJavaScript($context)",
}
```

And the result was surprising to me as well! (I redacted some of the values).

```json
{
  "authorizerContextUsernameProperty": "test-username",
  "wholeAuthorizerVariable": "{resourceId=w7badv0rv4, authorizer=, resourcePath=/, httpMethod=GET, extendedRequestId=OY6_EHr1DoEFSzg=, requestTime=03/Mar/2022:04:06:30 +0000, path=/prod/, accountId=XXX, protocol=HTTP/1.1, requestOverride=, stage=prod, domainPrefix=9sd680n5z5, requestTimeEpoch=1646280390676, requestId=f9393904-3014-43f6-8e24-f594db2d7604, identity=, domainName=9sd680n5z5.execute-api.eu-west-1.amazonaws.com, responseOverride=, apiId=9sd680n5z5}"
}
```

As you can see, the `authorizer`, `identity`, `responseOverride` properties are empty. Since I did not [override any request and response parameters](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-override-request-response-parameters.html), the emptiness of `responseOverride` property is understandable. But what about the `authorizer` and `identity`? I can see that `$context.authorizer.username` contains a value, otherwise the `authorizerContextUsernameProperty` property would be empty.

## What about AWS AppSync?

Like Amazon API Gateway, the [AWS AppSync](https://aws.amazon.com/appsync/) service enables developers to write VTL mapping templates to transform data. Will AWS AppSync VTL parsing logic behave differently from the one we observed in Amazon API Gateway? Let us find out.

![AWS AppSync setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/er5yssqbb6q8g9e793eg.jpeg)

The architecture is very similar to the one we have looked at previously. The main difference is that instead of making a GET request, we will be making a POST request since AWS AppSync exposes a GraphQL API.

### The mapping template and the response

The following is the AppSync resolver _request_ mapping template we will be working with. Check out [_resolver mapping template reference for Node data source documentation_](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-none.html) for more information.

> This article is not about AWS AppSync itself. If you are not familiar with the service, this [GitHub repository](https://github.com/dabit3/awesome-aws-appsync) contains a lot of AWS AppSync specific resources that you can consume.

```text
#set($payload = {
  "authorizerContextUsernameProperty": $context.identity.resolverContext.username,
  "wholeAuthorizerVariable": $util.escapeJavaScript($context.identity.resolverContext)
})

{
  "version": "2018-05-29",
  "payload": $util.toJson($payload),
}
```

If the mapping templates would work the same way as those declared in the context of Amazon API Gateway, the `wholeAuthorizerVariable` should return an empty string whenever I perform the GraphQL Query that uses this mapping template.

That is not the case. The following is the response I've got.

```json
{
  "data": {
    "getData": {
      "authorizerContextUsernameProperty": "test-username",
      "wholeAuthorizerVariable": "{username=test-username}"
    }
  }
}
```

The `wholeAuthorizerVariable` contains the key with a variable set by AWS Lambda authorizer – much different from how our sample Amazon API Gateway mapping template behaved.

If I were to amend the mapping template the following way:

```diff
#set($payload = {
  "authorizerContextUsernameProperty": $context.identity.resolverContext.username,
- "wholeAuthorizerVariable": $util.escapeJavaScript($context.identity.resolverContext)
+ "wholeAuthorizerVariable": $util.escapeJavaScript($context.identity)
})

{
  "version": "2018-05-29",
  "payload": $util.toJson($payload),
}
```

The GraphQL Query result is even more interesting as it leaks some service internals.

```json
{
  "data": {
    "getData": {
      "authorizerContextUsernameProperty": "test-username",
      "wholeAuthorizerVariable": "com.amazonaws.deepdish.common.identity.LambdaAuthIdentity@610a9a15"
    }
  }
}
```

I have no idea what `com.amazonaws.deepdish.common.identity.LambdaAuthIdentity@610a9a15` is.

## Bottom line

It appears that Amazon API Gateway evaluates the mapping template variables in a _lazy_ fashion. Only the "deepest" properties of a given built-in variable are evaluated at all.

The AWS AppSync acts a bit differently. As we saw earlier with the `$util.escapeJavaScript($context.identity.resolverContext)` returning the _stringified_ object.

After playing a bit more with the code, I have created the following rule of thumb I will be following from now on: _Whenever working with built-in mapping template variables, **always** work with the "deepest" properties of a given variable_.

## Closing words

I wrote this article because I've spent a lot of time debugging a "weird" behavior of Amazon API Gateway mapping templates. While the knowledge of what I wrote about is **not necessary** for you to develop incredible applications, I hope it could save you the time I've wasted.

For more AWS serverless content, consider following me on Twitter – [@wm_matuszewski](https://twitter.com/wm_matuszewski).

Thank you for your precious time.