Documentation
¶
Overview ¶
Package parser parses Lambda events, or the payload an event source wraps, into typed Go models and validates them: the Go side of Powertools for AWS Lambda (Python)'s parser utility, without Pydantic.
The models are Go structs: the aws-lambda-go events, the eventsource types, and your own. Parse decodes data into one with encoding/json/v2, then calls its Validate method, if it has one:
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
func (o Order) Validate() error {
if o.ID == "" {
return errors.New("id is required")
}
return nil
}
order, err := parser.Parse[Order](event.Body)
Validate is the counterpart of Pydantic's field and model validators. It is called on the decoded value and, when the model is a slice or an array, on each element. A failure, of decoding or of Validate, is a *ParseError, which carries the model, where the payload was and the cause.
Envelopes ¶
An envelope unwraps the payload of an event source: the body of an API Gateway request, the detail of an EventBridge event, the body of every SQS message. Each of Python's 17 envelopes is here twice:
a typed helper, for a handler that already receives the aws-lambda-go event: SQS, SNS, SNSSQS, EventBridge, APIGatewayREST, APIGatewayHTTP, APIGatewayWebSocket, FunctionURL, CloudWatchLogs, Kinesis, Firehose, Kafka, DynamoDBStream, VPCLattice, VPCLatticeV2, BedrockAgent, BedrockAgentFunction:
func handle(ctx context.Context, event events.SQSEvent) (events.SQSEventResponse, error) { orders, err := parser.SQS[Order](event) ... }
an Envelope value (SQSEnvelope, EventBridgeEnvelope...), for Parse and EventParser on the raw event. A source that batches records unwraps to a JSON array, so the model is a slice:
orders, err := parser.Parse[[]Order](raw, parser.WithEnvelope(parser.SQSEnvelope))
Payloads that are strings holding JSON (bodies, messages) are parsed as JSON; Kinesis and Firehose data and Kafka values are base64-decoded first, and Kinesis data that is not text is inflated as gzip or zlib. A missing payload (a GET request has no body) gives the zero model, not validated, as Python gives None: use a pointer model to tell it apart. DynamoDB stream records give DynamoDBImages, with nil for an image the record does not carry.
Implement Envelope to unwrap your own events, as Python's BaseEnvelope.
The decorator ¶
EventParser is Python's @event_parser: it turns a handler of a model into a handler of raw events that parses each event before calling it.
lambda.Start(parser.EventParser(handle, parser.WithEnvelope(parser.EventBridgeEnvelope)))
Nested JSON and extended models ¶
JSON[T] is a field AWS sends as a string holding JSON, Pydantic's Json[T]. Extending a built-in model, as Python subclasses one to type its payload, is Go embedding: the field declared next to the embedded event replaces the embedded one with the same JSON name.
type OrderEvent struct {
events.EventBridgeEvent
Detail Order `json:"detail"`
}
type OrderMessage struct {
events.SQSMessage
Body parser.JSON[Order] `json:"body"`
}
Decoding rules ¶
Decoding is lenient, as aws-lambda-go's: unknown members are ignored, missing ones stay zero; Validate checks what is required. encoding/json/v2 matches member names case-sensitively, except for the SNS notifications inside SQS messages, which spell two members differently (UnsubscribeURL for UnsubscribeUrl) and decode either way. WithJSONOptions passes options to the decoder: json.RejectUnknownMembers for Pydantic's extra="forbid", for example.
Differences from Python ¶
- No Pydantic: there are no built-in model validators. The Literal and format checks of Python's built-in models (eventSource values, HTTP methods, source IPs) are not enforced; the envelopes decode the outer event leniently, like aws-lambda-go, and Validate checks the payload.
- The model is a type parameter, inferred by EventParser from the handler: there is no model argument and no InvalidModelTypeError.
- A missing payload is the zero value of the model, not None.
- The HTTP envelopes honor isBase64Encoded; Python fails on encoded bodies.
- Kafka: the typed helper reads events.KafkaEvent, whose records are a map; it orders them by topic partition. KafkaEnvelope keeps the event's order, as Python does.
- DynamoDB numbers keep every digit as JSON numbers; binary attributes are base64 strings, sets are arrays.
- Pydantic's lax coercion ("123" into an int) does not happen; use the json "string" tag option where AWS sends numbers as strings.
Index ¶
- Variables
- func APIGatewayHTTP[T any](event events.APIGatewayV2HTTPRequest, opts ...Option) (T, error)
- func APIGatewayREST[T any](event events.APIGatewayProxyRequest, opts ...Option) (T, error)
- func APIGatewayWebSocket[T any](event events.APIGatewayWebsocketProxyRequest, opts ...Option) (T, error)
- func BedrockAgent[T any](event eventsource.BedrockAgentEvent, opts ...Option) (T, error)
- func BedrockAgentFunction[T any](event eventsource.BedrockAgentFunctionEvent, opts ...Option) (T, error)
- func CloudWatchLogs[T any](event events.CloudwatchLogsEvent, opts ...Option) ([]T, error)
- func EventBridge[T any](event events.EventBridgeEvent, opts ...Option) (T, error)
- func EventParser[T, R any](handler middleware.Handler[T, R], opts ...Option) middleware.Handler[jsontext.Value, R]
- func Firehose[T any](event events.KinesisFirehoseEvent, opts ...Option) ([]T, error)
- func FunctionURL[T any](event events.LambdaFunctionURLRequest, opts ...Option) (T, error)
- func Kafka[T any](event events.KafkaEvent, opts ...Option) ([]T, error)
- func Kinesis[T any](event events.KinesisEvent, opts ...Option) ([]T, error)
- func Parse[T any](data any, opts ...Option) (T, error)
- func SNS[T any](event events.SNSEvent, opts ...Option) ([]T, error)
- func SNSSQS[T any](event events.SQSEvent, opts ...Option) ([]T, error)
- func SQS[T any](event events.SQSEvent, opts ...Option) ([]T, error)
- func VPCLattice[T any](event eventsource.VPCLatticeEvent, opts ...Option) (T, error)
- func VPCLatticeV2[T any](event eventsource.VPCLatticeEventV2, opts ...Option) (T, error)
- type DynamoDBImages
- type Envelope
- type JSON
- type Option
- type ParseError
- type Validator
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidEnvelope reports an event that does not have the shape of // the envelope's event source. ErrInvalidEnvelope = errors.New("event does not match the envelope") // ErrInvalidPayload reports a payload that cannot be decoded: bad // base64, compressed data that does not inflate, text that is not UTF-8 // or not JSON. ErrInvalidPayload = errors.New("invalid payload") )
Errors of the envelopes.
var ErrParse = errors.New("parse error")
ErrParse is what every parsing failure matches with errors.Is.
Functions ¶
func APIGatewayHTTP ¶
func APIGatewayHTTP[T any](event events.APIGatewayV2HTTPRequest, opts ...Option) (T, error)
APIGatewayHTTP decodes the body of an API Gateway HTTP API request into a T.
func APIGatewayREST ¶
func APIGatewayREST[T any](event events.APIGatewayProxyRequest, opts ...Option) (T, error)
APIGatewayREST decodes the body of an API Gateway REST API proxy request into a T. A request without body gives the zero T.
func APIGatewayWebSocket ¶
func APIGatewayWebSocket[T any](event events.APIGatewayWebsocketProxyRequest, opts ...Option) (T, error)
APIGatewayWebSocket decodes the body of an API Gateway WebSocket API message into a T.
func BedrockAgent ¶
func BedrockAgent[T any](event eventsource.BedrockAgentEvent, opts ...Option) (T, error)
BedrockAgent decodes the input text of a Bedrock agent event into a T.
func BedrockAgentFunction ¶
func BedrockAgentFunction[T any](event eventsource.BedrockAgentFunctionEvent, opts ...Option) (T, error)
BedrockAgentFunction decodes the input text of a Bedrock agent function event into a T.
func CloudWatchLogs ¶
func CloudWatchLogs[T any](event events.CloudwatchLogsEvent, opts ...Option) ([]T, error)
CloudWatchLogs decodes the message of every log event of a CloudWatch Logs subscription into a T.
func EventBridge ¶
func EventBridge[T any](event events.EventBridgeEvent, opts ...Option) (T, error)
EventBridge decodes the detail of an EventBridge event into a T.
Example ¶
package main
import (
"encoding/json/jsontext"
"errors"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/alessandrolattao/powertools-lambda-go/parser"
)
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
func (o Order) Validate() error {
if o.ID == "" {
return errors.New("id is required")
}
return nil
}
func main() {
event := events.EventBridgeEvent{DetailType: "OrderPlaced", Detail: jsontext.Value(`{"id":"o-1","total":9.9}`)}
order, err := parser.EventBridge[Order](event)
if err != nil {
panic(err)
}
fmt.Println(order.ID)
}
Output: o-1
func EventParser ¶
func EventParser[T, R any](handler middleware.Handler[T, R], opts ...Option) middleware.Handler[jsontext.Value, R]
EventParser turns a handler of a typed model into a handler of raw events, Python's @event_parser: each invocation parses the event into a T (through the envelope given with WithEnvelope, if any), validates it, then calls handler with it. A parsing failure is returned as a *ParseError and the handler does not run.
lambda.Start(parser.EventParser(handle, parser.WithEnvelope(parser.SQSEnvelope)))
where handle is func(context.Context, []Order) (R, error). The model type is the handler's event type: Go infers it, as Python reads the type hint.
Example ¶
package main
import (
"context"
"encoding/json/jsontext"
"errors"
"fmt"
"github.com/alessandrolattao/powertools-lambda-go/parser"
)
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
func (o Order) Validate() error {
if o.ID == "" {
return errors.New("id is required")
}
return nil
}
func main() {
handle := func(_ context.Context, orders []Order) (int, error) {
return len(orders), nil
}
handler := parser.EventParser(handle, parser.WithEnvelope(parser.SQSEnvelope))
// lambda.Start(handler) passes the raw event.
n, err := handler(context.Background(), jsontext.Value(`{"Records":[{"body":"{\"id\":\"o-1\"}"}]}`))
if err != nil {
panic(err)
}
fmt.Println(n)
}
Output: 1
func Firehose ¶
func Firehose[T any](event events.KinesisFirehoseEvent, opts ...Option) ([]T, error)
Firehose decodes the data of every record of a Firehose transformation event into a T.
func FunctionURL ¶
func FunctionURL[T any](event events.LambdaFunctionURLRequest, opts ...Option) (T, error)
FunctionURL decodes the body of a Lambda Function URL request into a T.
func Kafka ¶
func Kafka[T any](event events.KafkaEvent, opts ...Option) ([]T, error)
Kafka decodes the value of every record of a Kafka event into a T. events.KafkaEvent holds the records in a map, which has no order: the records come sorted by topic partition, then in their order within it. Parse with KafkaEnvelope from the raw event to keep the event's order.
func Kinesis ¶
func Kinesis[T any](event events.KinesisEvent, opts ...Option) ([]T, error)
Kinesis decodes the data of every record of a Kinesis Data Streams event into a T.
func Parse ¶
Parse decodes data into a T and validates it. Data is raw JSON ([]byte, jsontext.Value, or a string holding JSON text, as Powertools Python parses a str) or any other value, which is marshalled to JSON first (a map, an aws-lambda-go event).
With WithEnvelope, the envelope unwraps the payload first; a missing payload (a GET request without body) gives the zero T, not validated, as Python gives None.
Decoding follows encoding/json/v2 defaults, as lenient as Pydantic's: unknown members are ignored, missing ones stay zero. Use Validate for required members and value constraints.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/alessandrolattao/powertools-lambda-go/parser"
)
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
func (o Order) Validate() error {
if o.ID == "" {
return errors.New("id is required")
}
return nil
}
func main() {
order, err := parser.Parse[Order](`{"id":"o-1","total":12.5}`)
if err != nil {
panic(err)
}
fmt.Println(order.ID, order.Total)
_, err = parser.Parse[Order](`{"total":3}`)
fmt.Println(err)
}
Output: o-1 12.5 parse parser_test.Order: id is required
func SNSSQS ¶
SNSSQS decodes the SNS message inside the body of every message of an SQS event into a T.
func SQS ¶
SQS decodes the body of every message of an SQS event into a T.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/alessandrolattao/powertools-lambda-go/parser"
)
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
func (o Order) Validate() error {
if o.ID == "" {
return errors.New("id is required")
}
return nil
}
func main() {
event := events.SQSEvent{Records: []events.SQSMessage{
{Body: `{"id":"o-1","total":1}`},
{Body: `{"id":"o-2","total":2}`},
}}
orders, err := parser.SQS[Order](event)
if err != nil {
panic(err)
}
fmt.Println(orders)
}
Output: [{o-1 1} {o-2 2}]
func VPCLattice ¶
func VPCLattice[T any](event eventsource.VPCLatticeEvent, opts ...Option) (T, error)
VPCLattice decodes the body of a VPC Lattice request (payload version 1) into a T.
func VPCLatticeV2 ¶
func VPCLatticeV2[T any](event eventsource.VPCLatticeEventV2, opts ...Option) (T, error)
VPCLatticeV2 decodes the body of a VPC Lattice request (payload version 2) into a T.
Types ¶
type DynamoDBImages ¶
type DynamoDBImages[T any] struct { NewImage *T `json:"NewImage"` OldImage *T `json:"OldImage"` }
DynamoDBImages holds the images of a DynamoDB stream record, decoded into a T: Python's {"NewImage": ..., "OldImage": ...}. An image the record does not carry (it depends on the stream view type and on the operation) is nil.
func DynamoDBStream ¶
func DynamoDBStream[T any](event events.DynamoDBEvent, opts ...Option) ([]DynamoDBImages[T], error)
DynamoDBStream decodes the images of every record of a DynamoDB stream event into a T. The result has one element per record.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/alessandrolattao/powertools-lambda-go/parser"
)
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
func (o Order) Validate() error {
if o.ID == "" {
return errors.New("id is required")
}
return nil
}
func main() {
event := events.DynamoDBEvent{Records: []events.DynamoDBEventRecord{{
Change: events.DynamoDBStreamRecord{NewImage: map[string]events.DynamoDBAttributeValue{
"id": events.NewStringAttribute("o-1"),
"total": events.NewNumberAttribute("12.5"),
}},
}}}
records, err := parser.DynamoDBStream[Order](event)
if err != nil {
panic(err)
}
fmt.Println(*records[0].NewImage, records[0].OldImage == nil)
}
Output: {o-1 12.5} true
func (DynamoDBImages[T]) Validate ¶
func (d DynamoDBImages[T]) Validate() error
Validate validates the images that are there.
type Envelope ¶
type Envelope interface {
// Unwrap returns the payload of the event as JSON (null when the event
// has none) or, for sources that batch records, a JSON array of the
// payloads.
Unwrap(event jsontext.Value) (jsontext.Value, error)
}
Envelope unwraps the payload of an event source from a raw event, for Parse and EventParser. The package has one for each source Powertools Python has an envelope for; implement it to unwrap your own events.
var APIGatewayHTTPEnvelope Envelope = apiGatewayHTTP
APIGatewayHTTPEnvelope unwraps the body of an API Gateway HTTP API request (payload version 2.0), Python's ApiGatewayV2Envelope.
var APIGatewayRESTEnvelope Envelope = apiGatewayREST
APIGatewayRESTEnvelope unwraps the body of an API Gateway REST API proxy request (payload version 1.0), Python's ApiGatewayEnvelope.
var APIGatewayWebSocketEnvelope Envelope = apiGatewayWebSocket
APIGatewayWebSocketEnvelope unwraps the body of an API Gateway WebSocket API message, Python's ApiGatewayWebSocketEnvelope.
var BedrockAgentEnvelope Envelope = bedrockAgent
BedrockAgentEnvelope unwraps the input text of a Bedrock agent OpenAPI action group event, Python's BedrockAgentEnvelope.
var BedrockAgentFunctionEnvelope Envelope = bedrockAgentFunction
BedrockAgentFunctionEnvelope unwraps the input text of a Bedrock agent function action group event, Python's BedrockAgentFunctionEnvelope.
var CloudWatchLogsEnvelope Envelope = cloudWatchLogs
CloudWatchLogsEnvelope unwraps the message of every log event of a CloudWatch Logs subscription, Python's CloudWatchLogsEnvelope.
var DynamoDBStreamEnvelope Envelope = dynamoDBStream
DynamoDBStreamEnvelope unwraps the images of every record of a DynamoDB stream event, Python's DynamoDBStreamEnvelope: the payload of a record is {"NewImage": ..., "OldImage": ...}, so the model to parse into is a slice of DynamoDBImages. Attribute values become plain JSON (see eventsource.DynamoDBImageJSON): numbers keep every digit, binary values are base64 strings.
var EventBridgeEnvelope Envelope = eventBridge
EventBridgeEnvelope unwraps the detail of an EventBridge event, Python's EventBridgeEnvelope.
var FirehoseEnvelope Envelope = firehose
FirehoseEnvelope unwraps the data of every record of an Amazon Data Firehose transformation event, Python's KinesisFirehoseEnvelope.
var FunctionURLEnvelope Envelope = functionURL
FunctionURLEnvelope unwraps the body of a Lambda Function URL request, Python's LambdaFunctionUrlEnvelope.
var KafkaEnvelope Envelope = kafkaEnvelope{}
KafkaEnvelope unwraps the value of every record of an Amazon MSK or self-managed Apache Kafka event, Python's KafkaEnvelope: base64-decoded, then parsed as JSON. Records come in the order of the event, topic partition after topic partition. A record without value (a tombstone) has no payload.
var KinesisEnvelope Envelope = kinesis
KinesisEnvelope unwraps the data of every record of a Kinesis Data Streams event, Python's KinesisDataStreamEnvelope. Data that is not UTF-8 text is inflated as gzip or zlib first.
var SNSEnvelope Envelope = sns
SNSEnvelope unwraps the message of every record of an SNS event, Python's SnsEnvelope.
var SNSSQSEnvelope Envelope = snsSQS
SNSSQSEnvelope unwraps the SNS message inside the body of every message of an SQS queue subscribed to an SNS topic (without raw message delivery), Python's SnsSqsEnvelope.
var SQSEnvelope Envelope = sqs
SQSEnvelope unwraps the body of every message of an SQS event, Python's SqsEnvelope.
var VPCLatticeEnvelope Envelope = vpcLattice
VPCLatticeEnvelope unwraps the body of a VPC Lattice request (payload version 1), Python's VpcLatticeEnvelope.
var VPCLatticeV2Envelope Envelope = vpcLatticeV2
VPCLatticeV2Envelope unwraps the body of a VPC Lattice request (payload version 2), Python's VpcLatticeV2Envelope.
type JSON ¶
type JSON[T any] struct { Value T }
JSON is a field that AWS sends as a string holding JSON, such as the body of an SQS message or the message of an SNS notification: Pydantic's Json[T]. It decodes the string, then its content into Value:
type OrderMessage struct {
events.SQSMessage
Body parser.JSON[Order] `json:"body"`
}
A field that already holds the JSON value itself, not a string, decodes too. It marshals back as a string holding JSON, the shape AWS sends.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/alessandrolattao/powertools-lambda-go/parser"
)
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
}
func (o Order) Validate() error {
if o.ID == "" {
return errors.New("id is required")
}
return nil
}
func main() {
type OrderMessage struct {
events.SQSMessage
Body parser.JSON[Order] `json:"body"`
}
message, err := parser.Parse[OrderMessage](`{"messageId":"m-1","body":"{\"id\":\"o-1\",\"total\":3}"}`)
if err != nil {
panic(err)
}
fmt.Println(message.MessageId, message.Body.Value.ID)
}
Output: m-1 o-1
func (JSON[T]) MarshalJSON ¶
MarshalJSON encodes Value as JSON inside a JSON string.
func (*JSON[T]) UnmarshalJSON ¶
UnmarshalJSON decodes a JSON string holding JSON, or a JSON value, into Value.
type Option ¶
type Option func(*config)
Option configures parsing.
func WithEnvelope ¶
WithEnvelope unwraps the payload of an event source before parsing: the model then describes the payload (or, for sources that batch records, a slice of payloads), not the event.
func WithJSONOptions ¶
WithJSONOptions passes options to encoding/json/v2 when decoding the model, for example json.RejectUnknownMembers(true) for Pydantic's extra="forbid", or json.MatchCaseInsensitiveNames(true).
type ParseError ¶
type ParseError struct {
// Model is the Go type the data was parsed into.
Model string
// Source says where the payload came from in the event, such as
// "Records[2].body"; it is empty when the whole data was parsed.
Source string
// Err is the cause: a *json.SemanticError, a *jsontext.SyntacticError,
// an envelope error, or the error Validate returned.
Err error
}
ParseError reports data that could not be decoded into the model, or that the model's Validate method rejected.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
func (*ParseError) Is ¶
func (*ParseError) Is(target error) bool
Is makes every ParseError match ErrParse.