UNPKG

@middy/event-normalizer

Version:

Parse and normalize AWS events middleware for the middy framework

190 lines (177 loc) 5.48 kB
import { jsonSafeParse } from '@middy/util' const defaults = { wrapNumbers: undefined } let _wrapNumbers const eventNormalizerMiddleware = (opts = {}) => { const { wrapNumbers } = { ...defaults, ...opts } _wrapNumbers = wrapNumbers const eventNormalizerMiddlewareBefore = async (request) => { parseEvent(request.event) } return { before: eventNormalizerMiddlewareBefore } } const parseEvent = (event) => { // event.eventSource => aws:amq, aws:docdb, aws:kafka, SelfManagedKafka // event.deliveryStreamArn => aws:lambda:events let eventSource = event.eventSource ?? event.deliveryStreamArn // event.Records => default // event.records => aws:lambda:events // event.messages => aws:amq // event.tasks => aws:s3:batch // event.events => aws:docdb const records = event.Records ?? event.records ?? event.messages ?? event.tasks ?? event.events if (!Array.isArray(records)) { // event.configRuleId => aws:config // event.awslogs => aws:cloudwatch // event['CodePipeline.job'] => aws:codepipeline eventSource ??= (event.configRuleId && 'aws:config') ?? (event.awslogs && 'aws:cloudwatch') ?? (event['CodePipeline.job'] && 'aws:codepipeline') if (eventSource) { events[eventSource]?.(event) } return } // record.eventSource => default // record.EventSource => aws:sns // record.s3Key => aws:s3:batch eventSource ??= records[0].eventSource ?? records[0].EventSource ?? (records[0].s3Key && 'aws:s3:batch') for (const record of records) { events[eventSource]?.(record) } } const normalizeS3KeyReplacePlus = /\+/g const events = { 'aws:amq': (message) => { message.data = base64Parse(message.data) }, 'aws:cloudwatch': (event) => { event.awslogs.data = base64Parse(event.awslogs.data) }, 'aws:codepipeline': (event) => { event[ 'CodePipeline.job' ].data.actionConfiguration.configuration.UserParameters = jsonSafeParse( event['CodePipeline.job'].data.actionConfiguration.configuration .UserParameters ) }, 'aws:config': (event) => { event.invokingEvent = jsonSafeParse(event.invokingEvent) event.ruleParameters = jsonSafeParse(event.ruleParameters) }, // 'aws:docdb': (record) => {}, 'aws:dynamodb': (record) => { record.dynamodb.Keys = unmarshall(record.dynamodb.Keys) record.dynamodb.NewImage = unmarshall(record.dynamodb.NewImage) record.dynamodb.OldImage = unmarshall(record.dynamodb.OldImage) }, 'aws:kafka': (event) => { for (const record in event.records) { for (const topic of event.records[record]) { topic.value &&= base64Parse(topic.value) } } }, // Kinesis Stream 'aws:kinesis': (record) => { record.kinesis.data = base64Parse(record.kinesis.data) }, // Kinesis Firehose 'aws:lambda:events': (record) => { record.data = base64Parse(record.data) }, 'aws:s3': (record) => { record.s3.object.key = normalizeS3Key(record.s3.object.key) }, 'aws:s3:batch': (task) => { task.s3Key = normalizeS3Key(task.s3Key) }, SelfManagedKafka: (event) => { events['aws:kafka'](event) }, 'aws:sns': (record) => { record.Sns.Message = jsonSafeParse(record.Sns.Message) parseEvent(record.Sns.Message) }, 'aws:sns:sqs': (record) => { record.Message = jsonSafeParse(record.Message) parseEvent(record.Message) }, 'aws:sqs': (record) => { record.body = jsonSafeParse(record.body) // SNS -> SQS Special Case if (record.body.Type === 'Notification') { events['aws:sns:sqs'](record.body) } else { parseEvent(record.body) } } } const base64Parse = (data) => jsonSafeParse(Buffer.from(data, 'base64').toString('utf-8')) const normalizeS3Key = (key) => decodeURIComponent(key.replace(normalizeS3KeyReplacePlus, ' ')) // decodeURIComponent(key.replaceAll('+', ' ')) // Start: AWS SDK unmarshall // Reference: https://github.com/aws/aws-sdk-js-v3/blob/v3.113.0/packages/util-dynamodb/src/convertToNative.ts const unmarshall = (data) => convertValue.M(data ?? {}) const convertValue = { NULL: () => null, BOOL: Boolean, N: (value) => { if (_wrapNumbers) { return { value } } const num = Number(value) if ( (Number.MAX_SAFE_INTEGER < num || num < Number.MIN_SAFE_INTEGER) && num !== Number.NEGATIVE_INFINITY && num !== Number.POSITIVE_INFINITY ) { try { return BigInt(value) } catch (error) { throw new Error( `${value} can't be converted to BigInt. Set options.wrapNumbers to get string value.` ) } } return num }, B: (value) => value, S: (value) => value, L: (value) => value.map((item) => convertToNative(item)), M: (value) => Object.entries(value).reduce((acc, [key, value]) => { acc[key] = convertToNative(value) return acc }, {}), NS: (value) => new Set(value.map(convertValue.N)), BS: (value) => new Set(value.map(convertValue.B)), SS: (value) => new Set(value.map(convertValue.S)) } const convertToNative = (data) => { for (const [key, value] of Object.entries(data)) { if (!convertValue[key]) { throw new Error(`Unsupported type passed: ${key}`, { cause: { package: '@middy/event-normalizer' } }) } if (typeof value === 'undefined') continue return convertValue[key](value) } } // End: AWS SDK unmarshall export default eventNormalizerMiddleware