Skip to content

Commit

Permalink
fix(parser): fix cause errors nested structure (#3250)
Browse files Browse the repository at this point in the history
  • Loading branch information
am29d authored Oct 25, 2024
1 parent 62eb2eb commit 1ff97cb
Show file tree
Hide file tree
Showing 20 changed files with 142 additions and 79 deletions.
7 changes: 2 additions & 5 deletions packages/parser/src/envelopes/apigw.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ZodSchema, z } from 'zod';
import { ParseError } from '../errors.js';
import { APIGatewayProxyEventSchema } from '../schemas/apigw.js';
import type { ParsedResult } from '../types/parser.js';
import type { ParsedResult } from '../types';
import { Envelope } from './envelope.js';

/**
Expand All @@ -12,10 +12,7 @@ export const ApiGatewayEnvelope = {
return Envelope.parse(APIGatewayProxyEventSchema.parse(data).body, schema);
},

safeParse<T extends ZodSchema>(
data: unknown,
schema: T
): ParsedResult<unknown, z.infer<T>> {
safeParse<T extends ZodSchema>(data: unknown, schema: T): ParsedResult {
const parsedEnvelope = APIGatewayProxyEventSchema.safeParse(data);
if (!parsedEnvelope.success) {
return {
Expand Down
1 change: 1 addition & 0 deletions packages/parser/src/envelopes/apigwv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const ApiGatewayV2Envelope = {
};
}

// use type assertion to avoid type check, we know it's success here
return parsedBody;
},
};
20 changes: 5 additions & 15 deletions packages/parser/src/envelopes/envelope.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ZodSchema, z } from 'zod';
import { ParseError } from '../errors.js';
import type { ParsedResult } from '../types/parser.js';
import type { ParsedResult } from '../types';

export const Envelope = {
/**
Expand Down Expand Up @@ -35,18 +35,14 @@ export const Envelope = {
* @param input
* @param schema
*/
safeParse<T extends ZodSchema>(
input: unknown,
schema: T
): ParsedResult<unknown, z.infer<T>> {
safeParse<T extends ZodSchema>(input: unknown, schema: T): ParsedResult {
try {
if (typeof input !== 'object' && typeof input !== 'string') {
return {
success: false,
error: new ParseError(
error: new Error(
`Invalid data type for envelope. Expected string or object, got ${typeof input}`
),
originalEvent: input,
};
}

Expand All @@ -61,18 +57,12 @@ export const Envelope = {
}
: {
success: false,
error: new ParseError('Failed to parse envelope', {
cause: parsed.error,
}),
originalEvent: input,
error: parsed.error,
};
} catch (e) {
return {
success: false,
error: new ParseError('Failed to parse envelope', {
cause: e as Error,
}),
originalEvent: input,
error: e as Error,
};
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/parser/src/envelopes/kafka.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Envelope } from './envelope.js';

export const KafkaEnvelope = {
parse<T extends ZodSchema>(data: unknown, schema: T): z.infer<T>[] {
// manually fetch event source to deside between Msk or SelfManaged
// manually fetch event source to decide between Msk or SelfManaged
const eventSource = (data as KafkaMskEvent).eventSource;

const parsedEnvelope:
Expand Down
2 changes: 1 addition & 1 deletion packages/parser/src/types/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type ParsedResultSuccess<Output> = {
type ParsedResultError<Input> = {
success: false;
error: ZodError | Error;
originalEvent: Input;
originalEvent?: Input;
};

/**
Expand Down
7 changes: 2 additions & 5 deletions packages/parser/tests/unit/envelope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import { ZodError, z } from 'zod';
import { Envelope } from '../../src/envelopes/envelope.js';
import { ParseError } from '../../src/errors.js';

describe('envelope: ', () => {
describe('parseSafe', () => {
Expand Down Expand Up @@ -37,8 +36,7 @@ describe('envelope: ', () => {
);
expect(result).toEqual({
success: false,
error: expect.any(ParseError),
originalEvent: { name: 123 },
error: expect.any(Error),
});
});

Expand All @@ -49,8 +47,7 @@ describe('envelope: ', () => {
);
expect(result).toEqual({
success: false,
error: expect.any(ParseError),
originalEvent: '{name: "John"}',
error: expect.any(Error),
});
});
});
Expand Down
9 changes: 9 additions & 0 deletions packages/parser/tests/unit/envelopes/apigw.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* @group unit/parser/envelopes/apigw
*/

import { ZodError } from 'zod';
import { ApiGatewayEnvelope } from '../../../src/envelopes/index.js';
import { ParseError } from '../../../src/errors.js';
import type { APIGatewayProxyEvent } from '../../../src/types/schema.js';
Expand Down Expand Up @@ -68,6 +69,10 @@ describe('API Gateway REST Envelope', () => {
error: expect.any(ParseError),
originalEvent: event,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(ZodError);
}
});

it('should not throw if the body is null', () => {
Expand All @@ -84,6 +89,10 @@ describe('API Gateway REST Envelope', () => {
error: expect.any(ParseError),
originalEvent: event,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(ZodError);
}
});

it('should not throw if the event is invalid', () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/parser/tests/unit/envelopes/apigwv2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* @group unit/parser/envelopes/apigwv2
*/

import { ZodError } from 'zod';
import { ApiGatewayV2Envelope } from '../../../src/envelopes/index.js';
import { ParseError } from '../../../src/errors.js';
import type { APIGatewayProxyEventV2 } from '../../../src/types/schema.js';
Expand Down Expand Up @@ -68,6 +69,10 @@ describe('API Gateway HTTP Envelope', () => {
error: expect.any(ParseError),
originalEvent: event,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(ZodError);
}
});

it('should not throw if the body is undefined', () => {
Expand Down
10 changes: 8 additions & 2 deletions packages/parser/tests/unit/envelopes/cloudwatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { gzipSync } from 'node:zlib';
import { generateMock } from '@anatine/zod-mock';
import { ZodError } from 'zod';
import { ParseError } from '../../../src';
import { CloudWatchEnvelope } from '../../../src/envelopes/index.js';
import {
Expand Down Expand Up @@ -113,11 +114,16 @@ describe('CloudWatch', () => {
Buffer.from(JSON.stringify(logMock), 'utf8')
).toString('base64');

expect(CloudWatchEnvelope.safeParse(testEvent, TestSchema)).toEqual({
const parseResult = CloudWatchEnvelope.safeParse(testEvent, TestSchema);
expect(parseResult).toEqual({
success: false,
error: expect.any(Error),
error: expect.any(ParseError),
originalEvent: testEvent,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(ZodError);
}
});

it('should return success false when envelope does not match', () => {
Expand Down
13 changes: 10 additions & 3 deletions packages/parser/tests/unit/envelopes/dynamodb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { generateMock } from '@anatine/zod-mock';
import type { AttributeValue, DynamoDBStreamEvent } from 'aws-lambda';
import { z } from 'zod';
import { ZodError, z } from 'zod';
import { DynamoDBStreamEnvelope } from '../../../src/envelopes/index.js';
import { ParseError } from '../../../src/errors.js';
import { TestEvents } from '../schema/utils.js';
Expand Down Expand Up @@ -93,12 +93,19 @@ describe('DynamoDB', () => {
(invalidDDBEvent.Records[1].dynamodb!.OldImage as typeof mockOldImage) =
mockOldImage;

const parsed = DynamoDBStreamEnvelope.safeParse(invalidDDBEvent, schema);
expect(parsed).toEqual({
const parseResult = DynamoDBStreamEnvelope.safeParse(
invalidDDBEvent,
schema
);
expect(parseResult).toEqual({
success: false,
error: expect.any(ParseError),
originalEvent: invalidDDBEvent,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(ZodError);
}
});

it('safeParse should return error if OldImage is invalid', () => {
Expand Down
15 changes: 11 additions & 4 deletions packages/parser/tests/unit/envelopes/eventbridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { generateMock } from '@anatine/zod-mock';
import type { EventBridgeEvent } from 'aws-lambda';
import { ZodError } from 'zod';
import { EventBridgeEnvelope } from '../../../src/envelopes/index.js';
import { ParseError } from '../../../src/errors.js';
import { TestEvents, TestSchema } from '../schema/utils.js';
Expand Down Expand Up @@ -85,13 +86,19 @@ describe('EventBridgeEnvelope ', () => {
foo: 'bar',
};

expect(
EventBridgeEnvelope.safeParse(eventBridgeEvent, TestSchema)
).toEqual({
const parseResult = EventBridgeEnvelope.safeParse(
eventBridgeEvent,
TestSchema
);
expect(parseResult).toEqual({
success: false,
error: expect.any(ParseError),
originalEvent: eventBridgeEvent,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(ZodError);
}
});

it('should safe parse eventbridge event and return original event if invalid data type', () => {
Expand All @@ -106,7 +113,7 @@ describe('EventBridgeEnvelope ', () => {
EventBridgeEnvelope.safeParse(eventBridgeEvent, TestSchema)
).toEqual({
success: false,
error: expect.any(Error),
error: expect.any(ParseError),
originalEvent: eventBridgeEvent,
});
});
Expand Down
13 changes: 9 additions & 4 deletions packages/parser/tests/unit/envelopes/kafka.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { generateMock } from '@anatine/zod-mock';
import type { MSKEvent, SelfManagedKafkaEvent } from 'aws-lambda';
import { ParseError } from '../../../src';
import { KafkaEnvelope } from '../../../src/envelopes/index.js';
import { TestEvents, TestSchema } from '../schema/utils.js';

Expand Down Expand Up @@ -76,18 +77,22 @@ describe('Kafka', () => {
const kafkaEvent = TestEvents.kafkaEventMsk as MSKEvent;
kafkaEvent.records['mytopic-0'][0].value = 'not a valid json';

const result = KafkaEnvelope.safeParse(kafkaEvent, TestSchema);
const parseResult = KafkaEnvelope.safeParse(kafkaEvent, TestSchema);

expect(result).toEqual({
expect(parseResult).toEqual({
success: false,
error: expect.any(Error),
error: expect.any(ParseError),
originalEvent: kafkaEvent,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(SyntaxError);
}
});
it('should return original event and error if envelope is invalid', () => {
expect(KafkaEnvelope.safeParse({ foo: 'bar' }, TestSchema)).toEqual({
success: false,
error: expect.any(Error),
error: expect.any(ParseError),
originalEvent: { foo: 'bar' },
});
});
Expand Down
28 changes: 13 additions & 15 deletions packages/parser/tests/unit/envelopes/kinesis-firehose.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/

import { generateMock } from '@anatine/zod-mock';
import type { z } from 'zod';
import { ZodError, type z } from 'zod';
import { ParseError } from '../../../src';
import { KinesisFirehoseEnvelope } from '../../../src/envelopes/index.js';
import type { KinesisFirehoseSchema } from '../../../src/schemas/';
import { TestEvents, TestSchema } from '../schema/utils.js';
Expand Down Expand Up @@ -128,13 +129,19 @@ describe('Kinesis Firehose Envelope', () => {
expect(resp).toEqual({ success: true, data: [mock, mock] });
});
it('should return original event if envelope is invalid', () => {
expect(
KinesisFirehoseEnvelope.safeParse({ foo: 'bar' }, TestSchema)
).toEqual({
const parseResult = KinesisFirehoseEnvelope.safeParse(
{ foo: 'bar' },
TestSchema
);
expect(parseResult).toEqual({
success: false,
error: expect.any(Error),
error: expect.any(ParseError),
originalEvent: { foo: 'bar' },
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(ZodError);
}
});
it('should return original event if record is not base64 encoded', () => {
const testEvent = TestEvents.kinesisFirehosePutEvent as z.infer<
Expand All @@ -147,18 +154,9 @@ describe('Kinesis Firehose Envelope', () => {

expect(KinesisFirehoseEnvelope.safeParse(testEvent, TestSchema)).toEqual({
success: false,
error: expect.any(Error),
error: expect.any(ParseError),
originalEvent: testEvent,
});
});
it('should return original event envelope is invalid', () => {
expect(
KinesisFirehoseEnvelope.safeParse({ foo: 'bar' }, TestSchema)
).toEqual({
success: false,
error: expect.any(Error),
originalEvent: { foo: 'bar' },
});
});
});
});
8 changes: 6 additions & 2 deletions packages/parser/tests/unit/envelopes/kinesis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@ describe('KinesisEnvelope', () => {
it('should return original event if record is invalid', () => {
const testEvent = TestEvents.kinesisStreamEvent as KinesisStreamEvent;
testEvent.Records[0].kinesis.data = 'invalid';
const resp = KinesisEnvelope.safeParse(testEvent, TestSchema);
expect(resp).toEqual({
const parseResult = KinesisEnvelope.safeParse(testEvent, TestSchema);
expect(parseResult).toEqual({
success: false,
error: expect.any(ParseError),
originalEvent: testEvent,
});

if (!parseResult.success && parseResult.error) {
expect(parseResult.error.cause).toBeInstanceOf(SyntaxError);
}
});
});
});
Loading

0 comments on commit 1ff97cb

Please sign in to comment.