Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions events/dynamodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

package events

import (
"encoding/json"
"time"
)

// The DynamoDBEvent stream event handled to Lambda
// http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-ddb-update
type DynamoDBEvent struct {
Expand Down Expand Up @@ -84,6 +89,10 @@ type DynamoDBStreamRecord struct {
// epoch time (http://www.epochconverter.com/) format.
ApproximateCreationDateTime SecondsEpochTime `json:"ApproximateCreationDateTime,omitempty"`

// The precision of ApproximateCreationDateTime when the timestamp is not in
// seconds.
ApproximateCreationDateTimePrecision string `json:"ApproximateCreationDateTimePrecision,omitempty"`

// The primary key attribute(s) for the DynamoDB item that was modified.
Keys map[string]DynamoDBAttributeValue `json:"Keys,omitempty"`

Expand All @@ -104,6 +113,74 @@ type DynamoDBStreamRecord struct {
StreamViewType string `json:"StreamViewType"`
}

const (
dynamoDBApproximateCreationDateTimePrecisionMillisecond = "MILLISECOND"
dynamoDBApproximateCreationDateTimePrecisionMicrosecond = "MICROSECOND"
)

func (r DynamoDBStreamRecord) MarshalJSON() ([]byte, error) {
type dynamoDBStreamRecord DynamoDBStreamRecord
type dynamoDBStreamRecordJSON struct {
ApproximateCreationDateTime interface{} `json:"ApproximateCreationDateTime,omitempty"`
*dynamoDBStreamRecord
}

record := dynamoDBStreamRecord(r)
v := dynamoDBStreamRecordJSON{
ApproximateCreationDateTime: r.ApproximateCreationDateTime,
dynamoDBStreamRecord: &record,
}

switch r.ApproximateCreationDateTimePrecision {
case dynamoDBApproximateCreationDateTimePrecisionMillisecond:
v.ApproximateCreationDateTime = r.ApproximateCreationDateTime.UnixNano() / int64(time.Millisecond)
case dynamoDBApproximateCreationDateTimePrecisionMicrosecond:
v.ApproximateCreationDateTime = r.ApproximateCreationDateTime.UnixNano() / int64(time.Microsecond)
}

return json.Marshal(v)
}

func (r *DynamoDBStreamRecord) UnmarshalJSON(data []byte) error {
type dynamoDBStreamRecord DynamoDBStreamRecord
type dynamoDBStreamRecordJSON struct {
ApproximateCreationDateTime json.RawMessage `json:"ApproximateCreationDateTime,omitempty"`
*dynamoDBStreamRecord
}

record := dynamoDBStreamRecord(*r)
v := dynamoDBStreamRecordJSON{dynamoDBStreamRecord: &record}
if err := json.Unmarshal(data, &v); err != nil {
return err
}

*r = DynamoDBStreamRecord(record)
if len(v.ApproximateCreationDateTime) == 0 {
return nil
}

switch r.ApproximateCreationDateTimePrecision {
case dynamoDBApproximateCreationDateTimePrecisionMillisecond:
var epoch int64
if err := json.Unmarshal(v.ApproximateCreationDateTime, &epoch); err != nil {
return err
}
r.ApproximateCreationDateTime = SecondsEpochTime{time.Unix(0, epoch*int64(time.Millisecond))}
case dynamoDBApproximateCreationDateTimePrecisionMicrosecond:
var epoch int64
if err := json.Unmarshal(v.ApproximateCreationDateTime, &epoch); err != nil {
return err
}
r.ApproximateCreationDateTime = SecondsEpochTime{time.Unix(0, epoch*int64(time.Microsecond))}
default:
if err := r.ApproximateCreationDateTime.UnmarshalJSON(v.ApproximateCreationDateTime); err != nil {
return err
}
}

return nil
}

type DynamoDBKeyType string

const (
Expand Down
26 changes: 26 additions & 0 deletions events/dynamodb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package events
import (
"encoding/json"
"testing"
"time"

"github.com/aws/aws-lambda-go/events/test"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -35,6 +36,31 @@ func TestDynamoDBEventMarshalingMalformedJson(t *testing.T) {
test.TestMalformedJson(t, DynamoDBEvent{})
}

func TestDynamoDBStreamRecordUnmarshalMicrosecondCreationDateTime(t *testing.T) {
inputJSON := []byte(`{
"ApproximateCreationDateTime": 1731101300058336,
"ApproximateCreationDateTimePrecision": "MICROSECOND",
"SequenceNumber": "1",
"SizeBytes": 1,
"StreamViewType": "NEW_IMAGE"
}`)

var inputRecord DynamoDBStreamRecord
if err := json.Unmarshal(inputJSON, &inputRecord); err != nil {
t.Errorf("could not unmarshal stream record. details: %v", err)
}

creationDateTime := inputRecord.ApproximateCreationDateTime
assert.Equal(t, time.Date(2024, time.November, 8, 21, 28, 20, 58336000, time.UTC), creationDateTime.UTC())
assert.Equal(t, "MICROSECOND", inputRecord.ApproximateCreationDateTimePrecision)

outputJSON, err := json.Marshal(inputRecord)
if err != nil {
t.Errorf("could not marshal stream record. details: %v", err)
}
assert.JSONEq(t, string(inputJSON), string(outputJSON))
}

func TestDynamoDBTimeWindowEventMarshaling(t *testing.T) {
// 1. read JSON from file
inputJSON := test.ReadJSONFromFile(t, "./testdata/dynamodb-time-window-event.json")
Expand Down
Loading