Skip to content

Commit 95762ad

Browse files
authored
feat(parser): simplify ParseResult and parse inference (#3568)
1 parent c109494 commit 95762ad

File tree

5 files changed

+167
-31
lines changed

5 files changed

+167
-31
lines changed

Diff for: packages/parser/src/parser.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ZodSchema, z } from 'zod';
22
import { ParseError } from './errors.js';
3-
import type { Envelope, ParsedResult } from './types/index.js';
3+
import type { Envelope } from './types/index.js';
4+
import type { ParseFunction } from './types/parser.js';
45

56
/**
67
* Parse the data using the provided schema, envelope and safeParse flag
@@ -27,12 +28,12 @@ import type { Envelope, ParsedResult } from './types/index.js';
2728
* @param schema the schema to use
2829
* @param safeParse whether to use safeParse or not, if true it will return a ParsedResult with the original event if the parsing fails
2930
*/
30-
const parse = <T extends ZodSchema, E extends Envelope>(
31+
const parse: ParseFunction = <T extends ZodSchema, E extends Envelope>(
3132
data: z.infer<T>,
3233
envelope: E | undefined,
3334
schema: T,
3435
safeParse?: boolean
35-
): ParsedResult | z.infer<T> => {
36+
) => {
3637
if (envelope && safeParse) {
3738
return envelope.safeParse(data, schema);
3839
}
@@ -56,10 +57,7 @@ const parse = <T extends ZodSchema, E extends Envelope>(
5657
* @param data the data to parse
5758
* @param schema the zod schema to use
5859
*/
59-
const safeParseSchema = <T extends ZodSchema>(
60-
data: z.infer<T>,
61-
schema: T
62-
): ParsedResult => {
60+
const safeParseSchema = <T extends ZodSchema>(data: z.infer<T>, schema: T) => {
6361
const result = schema.safeParse(data);
6462

6563
return result.success

Diff for: packages/parser/src/types/index.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,43 @@
11
export type {
2-
ParserOptions,
32
ParsedResult,
4-
ParsedResultSuccess,
53
ParsedResultError,
4+
ParsedResultSuccess,
5+
ParseFunction,
6+
ParserOptions,
67
} from '../types/parser.js';
78
export type { Envelope } from './envelope.js';
89

910
export type {
1011
ALBEvent,
11-
APIGatewayProxyEvent,
1212
ALBMultiValueHeadersEvent,
13+
APIGatewayProxyEvent,
1314
APIGatewayProxyEventV2,
14-
APIGatewayRequestContextV2,
1515
APIGatewayRequestAuthorizerV2,
16-
AppSyncResolverEvent,
16+
APIGatewayRequestContextV2,
1717
AppSyncBatchResolverEvent,
18-
S3Event,
19-
S3EventNotificationEventBridge,
20-
S3SqsEventNotification,
21-
SnsEvent,
22-
SqsEvent,
23-
DynamoDBStreamEvent,
24-
DynamoDBStreamToKinesisRecordEvent,
25-
CloudWatchLogsEvent,
18+
AppSyncResolverEvent,
2619
CloudFormationCustomResourceCreateEvent,
2720
CloudFormationCustomResourceDeleteEvent,
2821
CloudFormationCustomResourceUpdateEvent,
22+
CloudWatchLogsEvent,
23+
DynamoDBStreamEvent,
24+
DynamoDBStreamToKinesisRecordEvent,
2925
EventBridgeEvent,
30-
KafkaSelfManagedEvent,
3126
KafkaMskEvent,
27+
KafkaSelfManagedEvent,
3228
KinesisDataStreamEvent,
3329
KinesisDynamoDBStreamEvent,
3430
KinesisFireHoseEvent,
3531
KinesisFireHoseSqsEvent,
3632
LambdaFunctionUrlEvent,
33+
S3Event,
34+
S3EventNotificationEventBridge,
35+
S3ObjectLambdaEvent,
36+
S3SqsEventNotification,
3737
SesEvent,
38+
SnsEvent,
3839
SnsSqsNotification,
39-
S3ObjectLambdaEvent,
40+
SqsEvent,
4041
VpcLatticeEvent,
4142
VpcLatticeEventV2,
4243
} from './schema.js';

Diff for: packages/parser/src/types/parser.ts

+56-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ParsedResultError<Input> = {
3434
/**
3535
* The result of parsing an event using the safeParse, can either be a success or an error
3636
*/
37-
type ParsedResult<Input = unknown, Output = unknown> =
37+
type ParsedResult<Input = unknown, Output = Input> =
3838
| ParsedResultSuccess<Output>
3939
| ParsedResultError<Input>;
4040

@@ -54,7 +54,7 @@ type ZodInferredSafeParseResult<
5454
TSchema extends ZodSchema,
5555
TEnvelope extends Envelope,
5656
> = undefined extends TEnvelope
57-
? ParsedResult<unknown, z.infer<TSchema>>
57+
? ParsedResult<z.infer<TSchema>, z.infer<TSchema>>
5858
: TEnvelope extends ArrayEnvelope
5959
? ParsedResult<unknown, z.infer<TSchema>[]>
6060
: ParsedResult<unknown, z.infer<TSchema>>;
@@ -70,10 +70,63 @@ type ParserOutput<
7070
? ZodInferredSafeParseResult<TSchema, TEnvelope>
7171
: ZodInferredResult<TSchema, TEnvelope>;
7272

73+
/**
74+
* The parser function that can parse the data using the provided schema and envelope
75+
* we use function overloads to provide the correct return type based on the provided envelope
76+
**/
77+
type ParseFunction = {
78+
// No envelope cases
79+
<T extends ZodSchema>(
80+
data: z.infer<T>,
81+
envelope: undefined,
82+
schema: T,
83+
safeParse?: false
84+
): z.infer<T>;
85+
86+
<T extends ZodSchema>(
87+
data: z.infer<T>,
88+
envelope: undefined,
89+
schema: T,
90+
safeParse: true
91+
): ParsedResult<z.infer<T>>;
92+
93+
// Generic envelope case
94+
<T extends ZodSchema, E extends Envelope>(
95+
data: unknown,
96+
envelope: E,
97+
schema: T,
98+
safeParse?: false
99+
): E extends ArrayEnvelope ? z.infer<T>[] : z.infer<T>;
100+
101+
<T extends ZodSchema, E extends Envelope>(
102+
data: unknown,
103+
envelope: E,
104+
schema: T,
105+
safeParse: true
106+
): E extends ArrayEnvelope
107+
? ParsedResult<unknown, z.infer<T>[]>
108+
: ParsedResult<unknown, z.infer<T>>;
109+
110+
// Generic envelope case with safeParse
111+
<T extends ZodSchema, E extends Envelope, S extends boolean = false>(
112+
data: unknown,
113+
envelope: E,
114+
schema: T,
115+
safeParse?: S
116+
): S extends true
117+
? E extends ArrayEnvelope
118+
? ParsedResult<unknown, z.infer<T>[]>
119+
: ParsedResult<unknown, z.infer<T>>
120+
: E extends ArrayEnvelope
121+
? z.infer<T>[]
122+
: z.infer<T>;
123+
};
124+
73125
export type {
74-
ParserOptions,
126+
ParseFunction,
75127
ParsedResult,
76128
ParsedResultError,
77129
ParsedResultSuccess,
130+
ParserOptions,
78131
ParserOutput,
79132
};

Diff for: packages/parser/tests/types/envelopes.test.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { describe, expect, expectTypeOf, it } from 'vitest';
22
import { z } from 'zod';
33
import {
44
ApiGatewayEnvelope,
@@ -69,10 +69,6 @@ describe('Types ', () => {
6969
expect(Array.isArray(result)).toBe(true);
7070
expect(result).toEqual([{ name: 'John', age: 30 }]);
7171

72-
// Type assertion to ensure it's specifically User[]
73-
type AssertIsUserArray<T> = T extends z.infer<typeof userSchema>[]
74-
? true
75-
: false;
76-
type Test = AssertIsUserArray<Result>;
72+
expectTypeOf(result).toEqualTypeOf<z.infer<typeof userSchema>[]>();
7773
});
7874
});

Diff for: packages/parser/tests/types/parser.test.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { describe } from 'node:test';
2+
import { expect, expectTypeOf, it } from 'vitest';
3+
import { z } from 'zod';
4+
import { EventBridgeEnvelope, SqsEnvelope } from '../../src/envelopes/index.js';
5+
import { parse } from '../../src/parser.js';
6+
import type { EventBridgeEvent, SqsEvent } from '../../src/types/schema.js';
7+
import { getTestEvent } from '../unit/helpers/utils.js';
8+
9+
describe('Parser types', () => {
10+
const userSchema = z.object({
11+
name: z.string(),
12+
age: z.number(),
13+
});
14+
type User = z.infer<typeof userSchema>;
15+
const input = { name: 'John', age: 30 };
16+
17+
const eventBridgeBaseEvent = getTestEvent<EventBridgeEvent>({
18+
eventsPath: 'eventbridge',
19+
filename: 'base',
20+
});
21+
22+
const sqsBaseEvent = getTestEvent<SqsEvent>({
23+
eventsPath: 'sqs',
24+
filename: 'base',
25+
});
26+
it('infers return type for schema and safeParse', () => {
27+
// Act
28+
const result = parse(input, undefined, userSchema, true);
29+
30+
// Assert
31+
if (result.success) {
32+
expectTypeOf(result.data).toEqualTypeOf<User>();
33+
} else {
34+
expectTypeOf(result.originalEvent).toEqualTypeOf<User | undefined>();
35+
}
36+
});
37+
38+
it('infers return type for schema', () => {
39+
// Act
40+
const result = parse(input, undefined, userSchema);
41+
42+
// Assert
43+
expectTypeOf(result).toEqualTypeOf<User>();
44+
});
45+
46+
it('infers return type for schema and envelope', () => {
47+
// Prepare
48+
const event = structuredClone(eventBridgeBaseEvent);
49+
event.detail = input;
50+
51+
// Act
52+
const result = parse(event, EventBridgeEnvelope, userSchema);
53+
54+
// Assert
55+
expectTypeOf(result).toEqualTypeOf<User>();
56+
});
57+
58+
it('infert return type for schema, object envelope and safeParse', () => {
59+
// Prepare
60+
const event = structuredClone(eventBridgeBaseEvent);
61+
event.detail = input;
62+
63+
// Act
64+
const result = parse(event, EventBridgeEnvelope, userSchema, true);
65+
66+
// Assert
67+
if (result.success) {
68+
expectTypeOf(result.data).toEqualTypeOf<User>();
69+
expect(result.data).toEqual(input);
70+
} else {
71+
throw new Error('Parsing failed');
72+
}
73+
});
74+
75+
it('infers return type for schema, array envelope and safeParse', () => {
76+
// Prepare
77+
const event = structuredClone(sqsBaseEvent);
78+
event.Records[0].body = JSON.stringify(input);
79+
80+
// Act
81+
const result = parse(input, SqsEnvelope, userSchema, true);
82+
83+
// Assert
84+
if (result.success) {
85+
expectTypeOf(result.data).toEqualTypeOf<User[]>();
86+
}
87+
});
88+
});

0 commit comments

Comments
 (0)