aws

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package aws adapts Celerity handlers to AWS Lambda.

It is selected by importing it, which is how an application picks a platform without naming one anywhere else:

import _ "github.com/newstack-cloud/celerity-go-sdk/serverless/aws"

An init registers the adapter, and Detect answers from AWS_LAMBDA_FUNCTION_NAME, so that variable is known here and nowhere else in the SDK.

It implements serverless.Adapter: an event mapper between AWS event shapes and the SDK's vocabulary, and a Start that hands control to lambda.Start. Everything with a rule in it, resolving which handler this function serves, caching it across warm invocations, running the layer pipeline, and deciding what an unroutable event means, lives in the serverless package and is inherited rather than reimplemented here.

Mapping:

HTTP       API Gateway HTTP API v2 proxy  -> APIGatewayV2HTTPResponse
WebSocket  API Gateway WebSocket v2       -> {statusCode}
Consumer   SQS                            -> SQSEventResponse.BatchItemFailures
Schedule   EventBridge rule               -> nil
Custom     direct invoke payload          -> the handler's own output

What the platform decides rather than the SDK

A handler is written once and should see the same event whichever side mapped it. Where API Gateway has already made a decision the Celerity runtime does not, that decision stands and is documented on the mapping rather than undone:

  • Query parameters are read from the raw query string rather than from the platform's own joined map, so repetition survives exactly as the client sent it, which is what the runtime gives a handler.
  • Repeated headers arrive combined into one comma-separated value, and payload format 2.0 has no multi-value header field, so the division the client made is not recoverable. Headers whose grammar makes every comma a separator are divided back up, along with the few that clients actually repeat; the rest are carried whole, because splitting them would invent a division that may never have been made. HTTP-date headers are never divided: the comma after the day name is part of the date. See SplitHeaders and SplitHeadersEnvVar.
  • Cookies are never divided, the platform keeping them out of the headers for the same reason, so they are folded into a cookie header on the way in and carried as Cookies on the way out.
  • A catch-all path parameter arrives as one value with its separators still in it. It is split into segments, because that is the one case where not translating would change what a handler binding it receives.
  • A WebSocket connection carries text frames only, and a client sending a binary frame is disconnected, so the sender here implements handler.WebSocketSender and deliberately not handler.BinarySender.
  • API Gateway reports whether it accepted a pushed message and nothing about what the client made of it, so the acknowledgement fields on handler.OutboundMessage have nothing to wait for and are ignored.

Configuration

What this package reads from the environment is read once, when the adapter is built at process start, rather than per invocation: the environment does not change under a running function, and a warm function should not be able to change its behaviour beneath itself. The exception is Detect, which is asked once during adapter selection and decides whether this adapter runs at all.

Acknowledging a client's message

The WebSocket Runtime Protocol has a message that asked to be acknowledged acknowledged on receipt. The Celerity runtime does that itself; API Gateway does not, so the adapter does, before the handler runs and whether or not the message reaches a handler at all. An application implements none of it.

Because the transport carries no binary frames, the protocol's capabilities signal cannot reach a client here, and its absence is what tells the client it is in a constrained environment where acknowledgements are JSON text.

Bucket and datastore consumers

Every Celerity consumer arrives over SQS, including one bound to a bucket or a datastore: the notification is delivered to a queue and the message body carries the originating event. Those bodies are unwrapped, and S3 and DynamoDB event names are mapped onto Celerity's own vocabulary, so a handler bound to a bucket is given the object event rather than an SQS message that happens to contain one.

Reporting a failed message requires the function to be configured with ReportBatchItemFailures. Without it, SQS does not read the partial-failure response and deletes a batch where one message failed.

Index

Constants

View Source
const FunctionNameEnvVar = "AWS_LAMBDA_FUNCTION_NAME"

FunctionNameEnvVar is what every Lambda execution environment sets, and is how this adapter knows it is the one running.

View Source
const HandlerKindEnvVar = "CELERITY_HANDLER_TYPE"

HandlerKindEnvVar names the source a function serves, where the deployment knows it.

Consulted before the payload is looked at. A Celerity deployment sets it, and it is the only thing that can distinguish a custom invocation from a payload that happens to resemble another source, since a custom payload is the caller's own JSON and can hold anything.

View Source
const Name = "aws-lambda"

Name identifies this adapter in the handler manifest, in logs and in errors.

View Source
const SplitHeadersEnvVar = "CELERITY_SPLIT_HEADERS"

SplitHeadersEnvVar names further headers to divide back into the values the client sent, as a comma-separated list of header names.

CELERITY_SPLIT_HEADERS=x-tags,x-forwarded-host

This is additive, the names given here are divided in addition to SplitHeaders, never instead of them. The default list holds only headers that cannot contain a comma except as a separator, so there is nothing in it a deployment would need to take out.

Read once, when the adapter is built at process start. See Mapper.

Variables

View Source
var SplitHeaders = []string{
	"accept",
	"accept-charset",
	"accept-encoding",
	"accept-language",
	"access-control-request-headers",
	"cache-control",
	"connection",
	"content-encoding",
	"content-language",
	"content-type",
	"if-match",
	"if-none-match",
	"te",
	"trailer",
	"transfer-encoding",
	"upgrade",
	"vary",
	"via",
	"x-forwarded-for",
}

SplitHeaders are the headers divided at commas by default.

API Gateway joins repeated headers with a comma and payload format 2.0 has no multi-value header field, so a header that arrives with several values cannot be told from one value that contains a comma. Splitting is therefore a guess, and this list holds only the headers where the guess cannot be wrong as their grammar is a list of tokens, and a token may not contain a comma, so every comma in the value is a separator.

Two headers here are a deliberate, narrower judgement rather than a guarantee. A media-type parameter, a cache directive value and an entity tag may each be a quoted string, and a quoted string may contain a comma, so accept, content-type, cache-control, if-match and if-none-match can in principle be divided wrongly. They are included because they are the headers clients actually repeat.

Deliberately absent, and available through SplitHeadersEnvVar for a deployment that knows its own clients:

  • date, expires, if-modified-since, if-unmodified-since, last-modified and retry-after, which carry an HTTP-date, and an HTTP-date contains a comma after the day name: splitting "Mon, 19 Sep 2026 10:00:00 GMT" gives a handler "Mon". These are not judgement calls, they are wrong every time, and they appear on every conditional request.
  • user-agent, which is free-form

Functions

func ManagementEndpoint

func ManagementEndpoint(payload []byte) (string, error)

ManagementEndpoint returns the API Gateway Management API endpoint to push back to for the connection an event came from.

The event carries it, which is what lets one deployment serve several stages and domains without the endpoint being configured anywhere.

Types

type Adapter

type Adapter struct {
	// contains filtered or unexported fields
}

Adapter runs Celerity handlers on AWS Lambda.

An application does not construct one. Importing this package registers it, and it is selected at startup when FunctionNameEnvVar says the process is running on Lambda.

func New

func New() *Adapter

New returns the adapter, with its configuration captured from the environment.

Called from this package's init, so the environment is read once at process start rather than on an invocation. An application does not call it; a test that wants an adapter configured from what it has just set does.

func (*Adapter) AcknowledgeReceipt

func (a *Adapter) AcknowledgeReceipt(ctx context.Context, msg *handler.WebSocketMessage) error

AcknowledgeReceipt tells a client its message arrived, implementing serverless.ReceiptAcknowledger.

The protocol has a message that asked to be acknowledged on receipt, which is what lets a client stop its resend timer without waiting on however long the handler takes. API Gateway does not do it, so the SDK does, an application does not implement any of this, and a handler acknowledging its own messages would be reimplementing the protocol once per application.

func (*Adapter) Detect

func (a *Adapter) Detect() bool

Detect reports whether this process is running on Lambda.

Read live rather than captured, unlike the rest of the environment this package reads as it is asked once, while an adapter is being selected at startup, and it is the question that decides whether this adapter is used at all rather than configuration for one that already is.

func (*Adapter) Mapper

func (a *Adapter) Mapper() serverless.EventMapper

Mapper returns the event mapper.

func (*Adapter) Name

func (a *Adapter) Name() string

Name identifies the adapter.

func (*Adapter) Start

func (a *Adapter) Start(ctx context.Context, invoke serverless.Invoker) error

Start hands control to the Lambda runtime and does not return until the execution environment is shutting down.

The payload reaches the invoker as the bytes Lambda delivered. Lambda serialises whatever the invoker returns, which for each source is already the shape that source reads whether to be an API Gateway response, an SQS partial-failure report, or the handler's own output.

func (*Adapter) WebSocketSender

func (a *Adapter) WebSocketSender(
	_ context.Context,
	payload []byte,
) (handler.WebSocketSender, error)

WebSocketSender builds a sender for the connection an event came from, implementing serverless.WebSocketSenderProvider.

One sender per endpoint, kept for the life of the execution environment, so the AWS client and its credentials are built once rather than per invocation. Keyed by endpoint rather than held singly because the endpoint comes from the event, and a function can serve more than one stage or domain.

type Mapper

type Mapper struct {
	// contains filtered or unexported fields
}

Mapper translates between AWS event shapes and the SDK's vocabulary.

A mapper only translates, every method is a function of its payload and the configuration the mapper was built with.

That configuration is captured once, by NewMapper, rather than read from the environment per request. The environment does not change under a running function, so reading it on each invocation would be work to reach the same answer, and it would leave the behaviour of a warm function dependent on something that could in principle be changed beneath it.

func NewMapper

func NewMapper() Mapper

NewMapper returns a mapper configured from the environment.

func (Mapper) Detect

func (m Mapper) Detect(payload []byte) (handler.Kind, error)

Detect reports which source a payload came from.

A payload matching nothing is a custom invocation, it is the source with no shape of its own, being whatever the caller sent.

func (Mapper) FromBatchResult

func (m Mapper) FromBatchResult(res *handler.BatchResult) (any, error)

FromBatchResult maps a batch result into the partial-failure response SQS reads.

Naming a message leaves it on the queue to be retried or redriven, and naming none deletes the batch, so this is what decides delivery rather than only reporting what happened. The function must be configured with ReportBatchItemFailures for SQS to read it at all; without that, a batch where one message failed is deleted whole.

func (Mapper) FromCustomResult

func (m Mapper) FromCustomResult(res *handler.CustomInvokeResult) (any, error)

FromCustomResult returns the handler's output as the invocation's own.

Output is opaque to the SDK, and Lambda serialises what a function returns, so JSON output is passed through as itself rather than re-encoded into a quoted string. Output that is not JSON is returned as a string, which is what a caller reading the response can make sense of.

func (Mapper) FromResponse

func (m Mapper) FromResponse(res *handler.Response) (any, error)

FromResponse maps a handler's response into what API Gateway expects.

A body that is not valid UTF-8 is base64-encoded and declared as such, which is the only way the platform carries bytes. The declaration is what stops API Gateway from delivering the base64 text itself to the client.

func (Mapper) ToConsumerBatch

func (m Mapper) ToConsumerBatch(payload []byte, tag string) (*handler.ConsumerBatch, error)

ToConsumerBatch maps an SQS event into a consumer batch.

Every Celerity consumer arrives over SQS, including one bound to a bucket or a datastore, the notification is delivered to a queue, and the message body carries the originating event. Those are unwrapped so that a handler bound to a bucket is given the object event rather than an SQS message that happens to contain one, which is what it would receive under the Celerity runtime.

func (Mapper) ToCustomInvoke

func (m Mapper) ToCustomInvoke(payload []byte) (*handler.CustomInvoke, error)

ToCustomInvoke maps a direct invocation payload into a custom invoke.

The payload is the caller's own and is carried through untouched. The handler name comes from what the deployment set, since a direct invocation names the function rather than a handler within it.

An envelope of {"handlerName": ..., "payload": ...} is honoured where the deployment did not name anything, which is how one function serving several custom handlers is addressed. A payload that is not an envelope is the input, so an application whose own input happens to carry a handlerName field is only read that way when nothing else said which handler this is.

func (Mapper) ToRequest

func (m Mapper) ToRequest(payload []byte) (*handler.Request, error)

ToRequest maps an API Gateway HTTP API v2 proxy event into an HTTP request.

func (Mapper) ToScheduleTrigger

func (m Mapper) ToScheduleTrigger(payload []byte, tag string) (*handler.ScheduleTrigger, error)

ToScheduleTrigger maps an EventBridge event into a schedule trigger.

The schedule expression is not in the event. The rule holds it, and a function is not told the rule that invoked it. It is left empty rather than guessed at, so a handler that needs the expression reads it from its own configuration.

func (Mapper) ToWebSocketMessage

func (m Mapper) ToWebSocketMessage(payload []byte) (*handler.WebSocketMessage, error)

ToWebSocketMessage maps an API Gateway WebSocket v2 event into a WebSocket message.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL