---
title: "API Gateway Invocations"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{API Gateway Invocations}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

Invocations via an API Gateway require extra care when parsing the input event,
and when formatting results. For the most part, `lambdr` will handle this 
automatically. But for highly custom serialisation/deserialisation or edge cases
it may be helpful to understand how content via API Gateways is structured and
responded to.

## Events via API Gateways

When a Lambda runtime, like that implemented by `lambdr`, starts listening for
inputs it queries the "next invocation" HTTP endpoint. The response to this
is what's called here an _event_. The content of this event contains the
arguments (if any) to the function. After the result has been calculate, the
runtime must format the result and submit it to the "result" HTTP endpoint.

With direct invocation the content of the event contains a string which --- when interpreted as JSON --- gives the arguments for the handler function. When a
Lambda is invoked via an API Gateway then the content of the event contains significantly more detail and requires careful parsing.

There are two types of API Gateways to consider: REST and HTML.

### REST API Gateway events

An example of REST API Gateway event content is given at the bottom of this
section but the two most important sections are `body` and
`queryStringParameters`.

The `queryStringParameters` contain standard JSON. So if the request contains
a parameter `?number=9` in the query then the event will contain
`"queryStringParameters": {"number": "9"}` (note the string).

The `body` of the event contains the data passed during a `POST` operation.
It contains _stringified_ JSON. That is, instead of `"body": {"number": 9}`
we would see `"body": "{\"number\":9}"`.

Stringified JSON can be produced with the `as_stringified_json` helper
function which wraps `jsonlite`'s \code{\link[jsonlite]{toJSON}} function.
There is a slight difference in behaviour, as this function also
automatically unboxes singleton values and also represents `NULL`s as JSON
`nulls` --- a convention that is used by the event body provided by AWS
Lambda.

When the result is calculated and ready to be sent back to the AWS Lambda
response endpoint it must be formatted in a very specific way in order to be
compatible with the API Gateway. The correct format is as below, with the
`body` containing the _stringified_ JSON representation of the result. This
is given as an R list which is then converted to stringified JSON. Compare
this to the representation of the result for a direct invocation:

```r
## Formatting a result for a direct invocation
as_stringified_json(result)

## Formatting a result for an invocation via an API Gateway
as_stringified_json(
  list(
    isBase64Encoded = FALSE,
    statusCode = 200L,
    body = as_stringified_json(result)
  )
)
```

Here is an example event content from an invocation that is coming via an
API Gateway. The invocation is a call to a `parity` function with an argument
`number = 9`. Some information has been censored.

```json
{
"resource": "/parity",
"path": "/parity",
"httpMethod": "POST",
"headers": {
  "accept": "*/*",
  "Host": "abcdefghijk.execute-api.ap-southeast-2.amazonaws.com",
  "User-Agent": "curl/7.64.1",
  "X-Amzn-Trace-Id": "Root=1-615e4711-5f239aad2b046b5609e43b1c",
  "X-Forwarded-For": "192.168.1.1",
  "X-Forwarded-Port": "443",
  "X-Forwarded-Proto": "https"
},
"multiValueHeaders": {
  "accept": [
    "*/*"
  ],
  "Host": [
    "abcdefghijk.execute-api.ap-southeast-2.amazonaws.com"
  ],
  "User-Agent": [
    "curl/7.64.1"
  ],
  "X-Amzn-Trace-Id": [
    "Root=1-615e4711-5f239aad2b046b5609e43b1c"
  ],
  "X-Forwarded-For": [
    "192.168.1.1"
  ],
  "X-Forwarded-Port": [
    "443"
  ],
  "X-Forwarded-Proto": [
    "https"
  ]
},
"queryStringParameters": null,
"multiValueQueryStringParameters": null,
"pathParameters": null,
"stageVariables": null,
"requestContext": {
  "resourceId": "abcdef",
  "resourcePath": "/parity",
  "httpMethod": "POST",
  "extendedRequestId": "G0AKsFXISwMFsGA=",
  "requestTime": "07/Oct/2021:01:02:09 +0000",
  "path": "/test/parity",
  "accountId": "1234567890",
  "protocol": "HTTP/1.1",
  "stage": "test",
  "domainPrefix": "abcdefghijk",
  "requestTimeEpoch": 1633568529038,
  "requestId": "59bbb4c9-9d24-4cbb-941b-60dd4969e9c5",
  "identity": {
    "cognitoIdentityPoolId": null,
    "accountId": null,
    "cognitoIdentityId": null,
    "caller": null,
    "sourceIp": "192.168.1.1",
    "principalOrgId": null,
    "accessKey": null,
    "cognitoAuthenticationType": null,
    "cognitoAuthenticationProvider": null,
    "userArn": null,
    "userAgent": "curl/7.64.1",
    "user": null
  },
  "domainName": "abcdefghijk.execute-api.ap-southeast-2.amazonaws.com",
  "apiId": "abcdefghijk"
},
"body": "{\"number\":9}",
"isBase64Encoded": false
}
```

### HTML API Gateway events

HTML API Gateway events are similar to REST API Gateway events, except that the
body is more likely to be Base64 encoded, and the query parameters are presented
as string values. That is, we might expect to see a `rawQueryString` of "parameter1=value1&parameter1=value2&parameter2=value". In this case however the
`queryStringParameters` (if present) will be easier to work with:

```json
"queryStringParameters": {
    "parameter1": "value1,value2",
    "parameter2": "value"
  }
```

A full example of an event body is shown below. Note that the
`queryStringParameters` will not appear if `rawQueryString` is an empty string.

```json
{
    "version": "2.0",
    "routeKey": "ANY /parity",
    "rawPath": "/default/parity",
    "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
    "queryStringParameters": {
      "parameter1": "value1,value2",
      "parameter2": "value"
    },
    "headers": {
        "accept": "*/*",
        "content-length": "12",
        "content-type": "application/x-www-form-urlencoded",
        "host": "abcdefghi.execute-api.ap-southeast-2.amazonaws.com",
        "user-agent": "curl/7.64.1",
        "x-amzn-trace-id": "Root=1-6167f9fb-1ada874811eaf2bc1464c679",
        "x-forwarded-for": "192.168.1.1",
        "x-forwarded-port": "443",
        "x-forwarded-proto": "https"
    },
    "requestContext": {
        "accountId": "123456789",
        "apiId": "abcdefghi",
        "domainName": "abcdefghi.execute-api.ap-southeast-2.amazonaws.com",
        "domainPrefix": "abcdefghi",
        "http": {
            "method": "POST",
            "path": "/default/parity",
            "protocol": "HTTP/1.1",
            "sourceIp": "192.168.1.1",
            "userAgent": "curl/7.64.1"
        },
        "requestId": "HMP_QiusywMEPEg=",
        "routeKey": "ANY /parity",
        "stage": "default",
        "time": "14/Oct/2021:09:35:55 +0000",
        "timeEpoch": 1634204155055
    },
    "body": "eyJudW1iZXIiOjd9",
    "isBase64Encoded": true
}
```
