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