UNPKG

5.7 kBJavaScriptView Raw
1import { jsonSafeParse } from '@middy/util';
2const defaults = {
3 wrapNumbers: undefined
4};
5let _wrapNumbers;
6const eventNormalizerMiddleware = (opts = {})=>{
7 const { wrapNumbers } = {
8 ...defaults,
9 ...opts
10 };
11 _wrapNumbers = wrapNumbers;
12 const eventNormalizerMiddlewareBefore = async (request)=>{
13 parseEvent(request.event);
14 };
15 return {
16 before: eventNormalizerMiddlewareBefore
17 };
18};
19const parseEvent = (event)=>{
20 // event.eventSource => aws:amq, aws:docdb, aws:kafka, SelfManagedKafka
21 // event.deliveryStreamArn => aws:lambda:events
22 let eventSource = event.eventSource ?? event.deliveryStreamArn;
23 // event.Records => default
24 // event.records => aws:lambda:events
25 // event.messages => aws:amq
26 // event.tasks => aws:s3:batch
27 // event.events => aws:docdb
28 const records = event.Records ?? event.records ?? event.messages ?? event.tasks ?? event.events;
29 if (!Array.isArray(records)) {
30 // event.configRuleId => aws:config
31 // event.awslogs => aws:cloudwatch
32 // event['CodePipeline.job'] => aws:codepipeline
33 eventSource ??= (event.configRuleId && 'aws:config') ?? (event.awslogs && 'aws:cloudwatch') ?? (event['CodePipeline.job'] && 'aws:codepipeline');
34 if (eventSource) {
35 events[eventSource]?.(event);
36 }
37 return;
38 }
39 // record.eventSource => default
40 // record.EventSource => aws:sns
41 // record.s3Key => aws:s3:batch
42 eventSource ??= records[0].eventSource ?? records[0].EventSource ?? (records[0].s3Key && 'aws:s3:batch');
43 for (const record of records){
44 events[eventSource]?.(record);
45 }
46};
47const normalizeS3KeyReplacePlus = /\+/g;
48const events = {
49 'aws:amq': (message)=>{
50 message.data = base64Parse(message.data);
51 },
52 'aws:cloudwatch': (event)=>{
53 event.awslogs.data = base64Parse(event.awslogs.data);
54 },
55 'aws:codepipeline': (event)=>{
56 event['CodePipeline.job'].data.actionConfiguration.configuration.UserParameters = jsonSafeParse(event['CodePipeline.job'].data.actionConfiguration.configuration.UserParameters);
57 },
58 'aws:config': (event)=>{
59 event.invokingEvent = jsonSafeParse(event.invokingEvent);
60 event.ruleParameters = jsonSafeParse(event.ruleParameters);
61 },
62 // 'aws:docdb': (record) => {},
63 'aws:dynamodb': (record)=>{
64 record.dynamodb.Keys = unmarshall(record.dynamodb.Keys);
65 record.dynamodb.NewImage = unmarshall(record.dynamodb.NewImage);
66 record.dynamodb.OldImage = unmarshall(record.dynamodb.OldImage);
67 },
68 'aws:kafka': (event)=>{
69 for(const record in event.records){
70 for (const topic of event.records[record]){
71 topic.value &&= base64Parse(topic.value);
72 }
73 }
74 },
75 // Kinesis Stream
76 'aws:kinesis': (record)=>{
77 record.kinesis.data = base64Parse(record.kinesis.data);
78 },
79 // Kinesis Firehose
80 'aws:lambda:events': (record)=>{
81 record.data = base64Parse(record.data);
82 },
83 'aws:s3': (record)=>{
84 record.s3.object.key = normalizeS3Key(record.s3.object.key);
85 },
86 'aws:s3:batch': (task)=>{
87 task.s3Key = normalizeS3Key(task.s3Key);
88 },
89 SelfManagedKafka: (event)=>{
90 events['aws:kafka'](event);
91 },
92 'aws:sns': (record)=>{
93 record.Sns.Message = jsonSafeParse(record.Sns.Message);
94 parseEvent(record.Sns.Message);
95 },
96 'aws:sns:sqs': (record)=>{
97 record.Message = jsonSafeParse(record.Message);
98 parseEvent(record.Message);
99 },
100 'aws:sqs': (record)=>{
101 record.body = jsonSafeParse(record.body);
102 // SNS -> SQS Special Case
103 if (record.body.Type === 'Notification') {
104 events['aws:sns:sqs'](record.body);
105 } else {
106 parseEvent(record.body);
107 }
108 }
109};
110const base64Parse = (data)=>jsonSafeParse(Buffer.from(data, 'base64').toString('utf-8'));
111const normalizeS3Key = (key)=>decodeURIComponent(key.replace(normalizeS3KeyReplacePlus, ' ')) // decodeURIComponent(key.replaceAll('+', ' '))
112;
113// Start: AWS SDK unmarshall
114// Reference: https://github.com/aws/aws-sdk-js-v3/blob/v3.113.0/packages/util-dynamodb/src/convertToNative.ts
115const unmarshall = (data)=>convertValue.M(data ?? {});
116const convertValue = {
117 NULL: ()=>null,
118 BOOL: Boolean,
119 N: (value)=>{
120 if (_wrapNumbers) {
121 return {
122 value
123 };
124 }
125 const num = Number(value);
126 if ((Number.MAX_SAFE_INTEGER < num || num < Number.MIN_SAFE_INTEGER) && num !== Number.NEGATIVE_INFINITY && num !== Number.POSITIVE_INFINITY) {
127 try {
128 return BigInt(value);
129 } catch (error) {
130 throw new Error(`${value} can't be converted to BigInt. Set options.wrapNumbers to get string value.`);
131 }
132 }
133 return num;
134 },
135 B: (value)=>value,
136 S: (value)=>value,
137 L: (value)=>value.map((item)=>convertToNative(item)),
138 M: (value)=>Object.entries(value).reduce((acc, [key, value])=>{
139 acc[key] = convertToNative(value);
140 return acc;
141 }, {}),
142 NS: (value)=>new Set(value.map(convertValue.N)),
143 BS: (value)=>new Set(value.map(convertValue.B)),
144 SS: (value)=>new Set(value.map(convertValue.S))
145};
146const convertToNative = (data)=>{
147 for (const [key, value] of Object.entries(data)){
148 if (!convertValue[key]) throw new Error(`Unsupported type passed: ${key}`);
149 if (typeof value === 'undefined') continue;
150 return convertValue[key](value);
151 }
152};
153// End: AWS SDK unmarshall
154export default eventNormalizerMiddleware;
155