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

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/parser/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const JSONStringified = <T extends ZodType>(schema: T) =>
*/
const Base64Encoded = <T extends ZodType>(schema: T) =>
z
.string()
.base64()
.transform((data) => {
const decompressed = decompress(data);
const decoded = decoder.decode(fromBase64(data, 'base64'));
Expand Down
5 changes: 4 additions & 1 deletion packages/parser/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,11 @@ function parse<T extends StandardSchemaV1, E extends Envelope>(
}

const result = schema['~standard'].validate(data);
/* v8 ignore next -- @preserve */
if (result instanceof Promise) {
// Some validators (e.g. Zod) fall back to async validation when a transform
// throws synchronously, returning a rejected promise. Attach a handler so it
// does not surface as an unhandled rejection before we throw below.
result.catch(() => {});
throw new ParseError('Schema parsing supports only synchronous validation');
}

Expand Down
11 changes: 11 additions & 0 deletions packages/parser/tests/unit/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,17 @@ describe('Helper: Base64Encoded', () => {
expect(() => extendedSchema.parse(data)).toThrow();
});

it('returns a failed result instead of throwing when safeParse gets malformed base64', () => {
// Prepare
const schema = Base64Encoded(bodySchema);

// Act
const result = schema.safeParse('not base64!!');

// Assess
expect(result.success).toBe(false);
});

it('parses extended KinesisDataStreamSchema', () => {
// Prepare
const testEvent = getTestEvent<KinesisDataStreamEvent>({
Expand Down
40 changes: 39 additions & 1 deletion packages/parser/tests/unit/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { EventBridgeEnvelope } from '../../src/envelopes/eventbridge.js';
import { SqsEnvelope } from '../../src/envelopes/sqs.js';
import { ParseError } from '../../src/errors.js';
import { parse } from '../../src/parser.js';
import type { EventBridgeEvent, SqsEvent } from '../../src/types/index.js';
import { KinesisDataStreamSchema } from '../../src/schemas/kinesis.js';
import type {
EventBridgeEvent,
KinesisDataStreamEvent,
SqsEvent,
} from '../../src/types/index.js';
import { getTestEvent } from './helpers/utils.js';

describe('Parser', () => {
Expand Down Expand Up @@ -133,4 +138,37 @@ describe('Parser', () => {
originalEvent: event,
});
});

it('returns a failed result when a built-in schema carries malformed base64', () => {
// Prepare
const event = getTestEvent<KinesisDataStreamEvent>({
eventsPath: 'kinesis',
filename: 'stream',
});
event.Records[0].kinesis.data = 'not base64!!';

// Act
const result = parse(event, undefined, KinesisDataStreamSchema, true);

// Assess
expect(result).toStrictEqual({
success: false,
error: expect.any(ParseError),
originalEvent: event,
});
});

it('throws without leaking a rejection when a schema validates asynchronously', () => {
// Prepare
// A transform that throws synchronously makes Zod fall back to async
// validation, so `~standard.validate` returns a rejected promise
const schema = z.string().transform(() => {
throw new Error('cannot validate synchronously');
});

// Act & Assess
expect(() => parse('data', undefined, schema, true)).toThrow(
new ParseError('Schema parsing supports only synchronous validation')
);
});
});
Loading