Skip to content

Commit 8b0e77f

Browse files
authored
improv(parser): correctly infer helper return type (#2946)
1 parent d8cee04 commit 8b0e77f

File tree

2 files changed

+110
-113
lines changed

2 files changed

+110
-113
lines changed

packages/parser/src/helpers.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ZodSchema, z } from 'zod';
1+
import { type ZodTypeAny, z } from 'zod';
22

33
/**
44
* A helper function to parse a JSON string and validate it against a schema.
@@ -23,7 +23,7 @@ import { type ZodSchema, z } from 'zod';
2323
*
2424
* @param schema - The schema to validate the JSON string against
2525
*/
26-
const JSONStringified = (schema: ZodSchema) =>
26+
const JSONStringified = <T extends ZodTypeAny>(schema: T) =>
2727
z
2828
.string()
2929
.transform((str, ctx) => {

packages/parser/tests/unit/helpers.test.ts

+108-111
Original file line numberDiff line numberDiff line change
@@ -3,159 +3,156 @@
33
*
44
* @group unit/parser
55
*/
6-
76
import { z } from 'zod';
87
import { JSONStringified } from '../../src/helpers.js';
8+
import { AlbSchema } from '../../src/schemas/alb.js';
99
import {
10-
AlbSchema,
1110
SnsNotificationSchema,
1211
SnsRecordSchema,
13-
SqsRecordSchema,
14-
SqsSchema,
15-
} from '../../src/schemas';
16-
import type { SnsEvent, SqsEvent } from '../../src/types';
17-
import { getTestEvent } from './schema/utils';
12+
} from '../../src/schemas/sns.js';
13+
import { SqsRecordSchema, SqsSchema } from '../../src/schemas/sqs.js';
14+
import type { SnsEvent, SqsEvent } from '../../src/types/schema.js';
15+
import { getTestEvent } from './schema/utils.js';
16+
17+
const bodySchema = z.object({
18+
id: z.number(),
19+
name: z.string(),
20+
email: z.string().email(),
21+
});
22+
const envelopeSchema = z.object({
23+
body: z.string(),
24+
});
25+
const basePayload = {
26+
id: 1,
27+
name: 'John Doe',
28+
29+
};
1830

1931
describe('JSONStringified', () => {
20-
const schema = z.object({
21-
id: z.number(),
22-
name: z.string(),
23-
email: z.string().email(),
24-
});
25-
const baseSchema = z.object({
26-
body: z.string(),
27-
});
2832
it('should return a valid JSON', () => {
33+
// Prepare
2934
const data = {
30-
body: JSON.stringify({
31-
id: 1,
32-
name: 'John Doe',
33-
34-
}),
35+
body: JSON.stringify(structuredClone(basePayload)),
3536
};
3637

37-
const extendedSchema = baseSchema.extend({
38-
body: JSONStringified(schema),
38+
// Act
39+
const extendedSchema = envelopeSchema.extend({
40+
body: JSONStringified(bodySchema),
3941
});
4042

41-
const result = extendedSchema.parse(data);
42-
expect(result).toEqual({
43-
body: { id: 1, name: 'John Doe', email: '[email protected]' },
43+
// Assess
44+
expect(extendedSchema.parse(data)).toStrictEqual({
45+
body: basePayload,
4446
});
4547
});
4648

4749
it('should throw an error if the JSON payload is invalid', () => {
50+
// Prepare
4851
const data = {
49-
body: JSON.stringify({
50-
id: 1,
51-
name: 'John Doe',
52-
email: 'foo',
53-
}),
52+
body: JSON.stringify({ ...basePayload, email: 'invalid' }),
5453
};
5554

56-
const extendedSchema = baseSchema.extend({
57-
body: JSONStringified(schema),
55+
// Act
56+
const extendedSchema = envelopeSchema.extend({
57+
body: JSONStringified(bodySchema),
5858
});
5959

60+
// Assess
6061
expect(() => extendedSchema.parse(data)).toThrow();
6162
});
6263

6364
it('should throw an error if the JSON is malformed', () => {
65+
// Prepare
6466
const data = {
6567
body: 'invalid',
6668
};
6769

68-
const extendedSchema = baseSchema.extend({
69-
body: JSONStringified(schema),
70+
// Act
71+
const extendedSchema = envelopeSchema.extend({
72+
body: JSONStringified(bodySchema),
7073
});
7174

75+
// Assess
7276
expect(() => extendedSchema.parse(data)).toThrow();
7377
});
7478

75-
describe('should parse common built-in schemas', () => {
76-
const customSchema = z.object({
77-
id: z.number(),
78-
name: z.string(),
79-
email: z.string().email(),
79+
it('should parse extended AlbSchema', () => {
80+
// Prepare
81+
const testEvent = getTestEvent({
82+
eventsPath: '.',
83+
filename: 'albEvent',
8084
});
85+
testEvent.body = JSON.stringify(structuredClone(basePayload));
8186

82-
const payload = {
83-
id: 1,
84-
name: 'John Doe',
85-
86-
};
87+
// Act
88+
const extendedSchema = AlbSchema.extend({
89+
body: JSONStringified(bodySchema),
90+
});
8791

88-
it('should parse extended AlbSchema', () => {
89-
const extendedSchema = AlbSchema.extend({
90-
body: JSONStringified(customSchema),
91-
});
92-
93-
const testEvent = getTestEvent({
94-
eventsPath: '.',
95-
filename: 'albEvent',
96-
});
97-
testEvent.body = JSON.stringify(payload);
98-
99-
const result = extendedSchema.parse(testEvent);
100-
expect(result).toEqual({
101-
...testEvent,
102-
body: payload,
103-
});
92+
// Assess
93+
expect(extendedSchema.parse(testEvent)).toStrictEqual({
94+
...testEvent,
95+
body: basePayload,
10496
});
97+
});
10598

106-
it('should parse extended SqsSchema', () => {
107-
const extendedSchema = SqsSchema.extend({
108-
Records: z.array(
109-
SqsRecordSchema.extend({
110-
body: JSONStringified(customSchema),
111-
})
112-
),
113-
});
114-
115-
const testEvent = getTestEvent<SqsEvent>({
116-
eventsPath: '.',
117-
filename: 'sqsEvent',
118-
});
119-
testEvent.Records[0].body = JSON.stringify(payload);
120-
testEvent.Records[1].body = JSON.stringify(payload);
121-
122-
const result = extendedSchema.parse(testEvent);
123-
expect(result).toEqual({
124-
...testEvent,
125-
Records: [
126-
{ ...testEvent.Records[0], body: payload },
127-
{ ...testEvent.Records[1], body: payload },
128-
],
129-
});
99+
it('should parse extended SqsSchema', () => {
100+
// Prepare
101+
const testEvent = getTestEvent<SqsEvent>({
102+
eventsPath: '.',
103+
filename: 'sqsEvent',
104+
});
105+
const stringifiedBody = JSON.stringify(basePayload);
106+
testEvent.Records[0].body = stringifiedBody;
107+
testEvent.Records[1].body = stringifiedBody;
108+
109+
// Act
110+
const extendedSchema = SqsSchema.extend({
111+
Records: z.array(
112+
SqsRecordSchema.extend({
113+
body: JSONStringified(bodySchema),
114+
})
115+
),
116+
});
117+
118+
// Assess
119+
expect(extendedSchema.parse(testEvent)).toStrictEqual({
120+
...testEvent,
121+
Records: [
122+
{ ...testEvent.Records[0], body: basePayload },
123+
{ ...testEvent.Records[1], body: basePayload },
124+
],
125+
});
126+
});
127+
128+
it('should parse extended SnsSchema', () => {
129+
// Prepare
130+
const testEvent = getTestEvent<SnsEvent>({
131+
eventsPath: '.',
132+
filename: 'snsEvent',
133+
});
134+
testEvent.Records[0].Sns.Message = JSON.stringify(basePayload);
135+
136+
// Act
137+
const extendedSchema = SqsSchema.extend({
138+
Records: z.array(
139+
SnsRecordSchema.extend({
140+
Sns: SnsNotificationSchema.extend({
141+
Message: JSONStringified(bodySchema),
142+
}),
143+
})
144+
),
130145
});
131146

132-
it('should parse extended SnsSchema', () => {
133-
const extendedSchema = SqsSchema.extend({
134-
Records: z.array(
135-
SnsRecordSchema.extend({
136-
Sns: SnsNotificationSchema.extend({
137-
Message: JSONStringified(customSchema),
138-
}),
139-
})
140-
),
141-
});
142-
143-
const testEvent = getTestEvent<SnsEvent>({
144-
eventsPath: '.',
145-
filename: 'snsEvent',
146-
});
147-
testEvent.Records[0].Sns.Message = JSON.stringify(payload);
148-
149-
const result = extendedSchema.parse(testEvent);
150-
expect(result).toEqual({
151-
...testEvent,
152-
Records: [
153-
{
154-
...testEvent.Records[0],
155-
Sns: { ...testEvent.Records[0].Sns, Message: payload },
156-
},
157-
],
158-
});
147+
// Assess
148+
expect(extendedSchema.parse(testEvent)).toStrictEqual({
149+
...testEvent,
150+
Records: [
151+
{
152+
...testEvent.Records[0],
153+
Sns: { ...testEvent.Records[0].Sns, Message: basePayload },
154+
},
155+
],
159156
});
160157
});
161158
});

0 commit comments

Comments
 (0)