parser

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 16 Imported by: 0

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

Examples

Constants

This section is empty.

Variables

View Source
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.

View Source
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

func Parse[T any](data any, opts ...Option) (T, error)

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 SNS

func SNS[T any](event events.SNSEvent, opts ...Option) ([]T, error)

SNS decodes the message of every record of an SNS event into a T.

func SNSSQS

func SNSSQS[T any](event events.SQSEvent, opts ...Option) ([]T, error)

SNSSQS decodes the SNS message inside the body of every message of an SQS event into a T.

func SQS

func SQS[T any](event events.SQSEvent, opts ...Option) ([]T, error)

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

func (j JSON[T]) MarshalJSON() ([]byte, error)

MarshalJSON encodes Value as JSON inside a JSON string.

func (*JSON[T]) UnmarshalJSON

func (j *JSON[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string holding JSON, or a JSON value, into Value.

func (JSON[T]) Validate

func (j JSON[T]) Validate() error

Validate validates Value when it is a Validator, so that a model holding JSON fields can validate them from its own Validate method.

type Option

type Option func(*config)

Option configures parsing.

func WithEnvelope

func WithEnvelope(envelope Envelope) Option

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

func WithJSONOptions(opts ...json.Options) Option

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.

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

Unwrap returns the cause.

type Validator

type Validator interface {
	Validate() error
}

Validator is implemented by models that check themselves after decoding, the Go counterpart of Pydantic's field and model validators. Parse calls Validate on the decoded value, and on each element when the model is a slice or an array.

Jump to

Keyboard shortcuts

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