@aws-lambda-powertools/parser
Version:
The parser package for the Powertools for AWS Lambda (TypeScript) library.
132 lines (131 loc) • 4.99 kB
JavaScript
import { ZodError } from 'zod';
import { ParseError } from '../errors.js';
import { SnsSqsNotificationSchema } from '../schemas/sns.js';
import { SqsSchema } from '../schemas/sqs.js';
import { envelopeDiscriminator } from './envelope.js';
const createError = (index, issues) => ({
issues: issues.map((issue) => ({
...issue,
path: ['Records', index, 'body', ...issue.path],
})),
});
const parseStep = (parser, data, index) => {
const result = parser(data);
return result.success
? { success: true, data: result.data }
: {
success: false,
error: createError(index, result.error.issues),
};
};
/**
* SNS plus SQS Envelope to extract array of Records
*
* Published messages from SNS to SQS has a slightly different payload structure
* than regular SNS messages, and when sent to SQS, they are stringified into the
* `body` field of each SQS record.
*
* To parse the `Message` field of the SNS notification, we need to:
* 1. Parse SQS schema with incoming data
* 2. `JSON.parse()` the SNS payload and parse against SNS Notification schema
* 3. Finally, parse the payload against the provided schema
*/
export const SnsSqsEnvelope = {
/**
* This is a discriminator to differentiate whether an envelope returns an array or an object
* @hidden
*/
[envelopeDiscriminator]: 'array',
parse(data, schema) {
let parsedEnvelope;
try {
parsedEnvelope = SqsSchema.parse(data);
}
catch (error) {
throw new ParseError('Failed to parse SQS Envelope', {
cause: error,
});
}
return parsedEnvelope.Records.map((record, recordIndex) => {
try {
return schema.parse(SnsSqsNotificationSchema.parse(JSON.parse(record.body)).Message);
}
catch (error) {
throw new ParseError(`Failed to parse SQS Record at index ${recordIndex}`, {
cause: new ZodError(error instanceof ZodError
? error.issues.map((issue) => ({
...issue,
path: ['Records', recordIndex, 'body', ...issue.path],
}))
: [
{
code: 'custom',
input: record.body,
message: `Invalid JSON - ${error.message}`,
path: ['Records', recordIndex, 'body'],
},
]),
});
}
});
},
safeParse(data, schema) {
const parsedEnvelope = SqsSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
success: false,
error: new ParseError('Failed to parse SQS envelope', {
cause: parsedEnvelope.error,
}),
originalEvent: data,
};
}
const parseRecord = (record, index) => {
try {
const body = JSON.parse(record.body);
const notification = parseStep((data) => SnsSqsNotificationSchema.safeParse(data), body, index);
if (!notification.success)
return notification;
return parseStep((data) => schema.safeParse(data), notification.data.Message, index);
}
catch (error) {
return {
success: false,
error: createError(index, [
{
code: 'custom',
message: `Invalid JSON - ${error.message}`,
input: record.body,
path: [],
},
]),
};
}
};
const result = parsedEnvelope.data.Records.reduce((acc, record, index) => {
const parsed = parseRecord(record, index);
if (parsed.success) {
acc.records.push(parsed.data);
}
else {
acc.success = false;
acc.errors[index] = parsed.error;
}
return acc;
}, { success: true, records: [], errors: {} });
if (result.success) {
return { success: true, data: result.records };
}
const indexes = Object.keys(result.errors);
const errorMessage = indexes.length > 1
? `Failed to parse SQS Records at indexes ${indexes.join(', ')}`
: `Failed to parse SQS Record at index ${indexes[0]}`;
return {
success: false,
error: new ParseError(errorMessage, {
cause: new ZodError(Object.values(result.errors).flatMap((e) => e.issues)),
}),
originalEvent: data,
};
},
};