Skip to main content

src/aws/lambda/response.gleam

//// Typed responses for Lambda integrations that expect a structured reply,
//// with encoders that produce the JSON Lambda marshals back to the caller.
////
//// Use these as the `encode` argument to `aws/lambda.start_json`.

import gleam/dict.{type Dict}
import gleam/json.{type Json}
import gleam/list

// --- API Gateway / proxy integration response -----------------------------

/// A proxy-integration response. The same shape serves API Gateway REST
/// (payload format 1.0) and HTTP API (payload format 2.0) proxy
/// integrations, as well as Lambda function URLs. `cookies` is encoded only
/// when non-empty (it is an HTTP API 2.0 field).
pub type ProxyResponse {
  ProxyResponse(
    status_code: Int,
    headers: Dict(String, String),
    cookies: List(String),
    body: String,
    is_base64_encoded: Bool,
  )
}

/// A `200 OK` proxy response with a body and no extra headers. Set further
/// fields with record-update syntax:
///
/// ```gleam
/// response.proxy_response(201, "{}")
/// |> fn(r) { response.ProxyResponse(..r, headers: my_headers) }
/// ```
pub fn proxy_response(status_code: Int, body: String) -> ProxyResponse {
  ProxyResponse(
    status_code: status_code,
    headers: dict.new(),
    cookies: [],
    body: body,
    is_base64_encoded: False,
  )
}

/// Encode a [`ProxyResponse`](#ProxyResponse) to its Lambda JSON form.
pub fn proxy_to_json(response: ProxyResponse) -> Json {
  let base = [
    #("statusCode", json.int(response.status_code)),
    #("headers", string_dict_to_json(response.headers)),
    #("body", json.string(response.body)),
    #("isBase64Encoded", json.bool(response.is_base64_encoded)),
  ]
  let fields = case response.cookies {
    [] -> base
    cookies -> [#("cookies", json.array(cookies, json.string)), ..base]
  }
  json.object(fields)
}

// --- SQS partial batch response -------------------------------------------

/// Reports which messages in an SQS batch failed so Lambda redelivers only
/// those. Requires `ReportBatchItemFailures` on the event source mapping. An
/// empty list tells Lambda the whole batch succeeded.
pub type SqsBatchResponse {
  SqsBatchResponse(batch_item_failures: List(String))
}

/// Encode an [`SqsBatchResponse`](#SqsBatchResponse) to its Lambda JSON form.
pub fn sqs_batch_to_json(response: SqsBatchResponse) -> Json {
  json.object([
    #(
      "batchItemFailures",
      json.array(response.batch_item_failures, fn(id) {
        json.object([#("itemIdentifier", json.string(id))])
      }),
    ),
  ])
}

fn string_dict_to_json(values: Dict(String, String)) -> Json {
  values
  |> dict.to_list
  |> list.map(fn(pair) { #(pair.0, json.string(pair.1)) })
  |> json.object
}