Skip to content

Commit 9973f09

Browse files
am29ddreamorosi
andauthored
feat(parser): allow parser set event type of handler with middy (#2786)
Co-authored-by: Andrea Amorosi <[email protected]>
1 parent f641c90 commit 9973f09

File tree

13 files changed

+216
-102
lines changed

13 files changed

+216
-102
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class CloudWatchEnvelope extends Envelope {
1717
public static parse<T extends ZodSchema>(
1818
data: unknown,
1919
schema: T
20-
): z.infer<T> {
20+
): z.infer<T>[] {
2121
const parsedEnvelope = CloudWatchLogsSchema.parse(data);
2222

2323
return parsedEnvelope.awslogs.data.logEvents.map((record) => {

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@ import { DynamoDBStreamSchema } from '../schemas/index.js';
33
import type { ParsedResult, ParsedResultError } from '../types/index.js';
44
import { Envelope } from './envelope.js';
55
import { ParseError } from '../errors.js';
6-
7-
type DynamoDBStreamEnvelopeResponse<T extends ZodSchema> = {
8-
NewImage: z.infer<T>;
9-
OldImage: z.infer<T>;
10-
};
6+
import type { DynamoDBStreamEnvelopeResponse } from '../types/envelope.js';
117

128
/**
139
* DynamoDB Stream Envelope to extract data within NewImage/OldImage

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class KafkaEnvelope extends Envelope {
2020
public static parse<T extends ZodSchema>(
2121
data: unknown,
2222
schema: T
23-
): z.infer<T> {
23+
): z.infer<T>[] {
2424
// manually fetch event source to deside between Msk or SelfManaged
2525
const eventSource = (data as KafkaMskEvent)['eventSource'];
2626

Diff for: packages/parser/src/envelopes/kinesis-firehose.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class KinesisFirehoseEnvelope extends Envelope {
2020
public static parse<T extends ZodSchema>(
2121
data: unknown,
2222
schema: T
23-
): z.infer<T> {
23+
): z.infer<T>[] {
2424
const parsedEnvelope = KinesisFirehoseSchema.parse(data);
2525

2626
return parsedEnvelope.records.map((record) => {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class KinesisEnvelope extends Envelope {
1818
public static parse<T extends ZodSchema>(
1919
data: unknown,
2020
schema: T
21-
): z.infer<T> {
21+
): z.infer<T>[] {
2222
const parsedEnvelope = KinesisDataStreamSchema.parse(data);
2323

2424
return parsedEnvelope.Records.map((record) => {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class SnsEnvelope extends Envelope {
1818
public static parse<T extends ZodSchema>(
1919
data: unknown,
2020
schema: T
21-
): z.infer<T> {
21+
): z.infer<T>[] {
2222
const parsedEnvelope = SnsSchema.parse(data);
2323

2424
return parsedEnvelope.Records.map((record) => {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class SqsEnvelope extends Envelope {
1717
public static parse<T extends ZodSchema>(
1818
data: unknown,
1919
schema: T
20-
): z.infer<T> {
20+
): z.infer<T>[] {
2121
const parsedEnvelope = SqsSchema.parse(data);
2222

2323
return parsedEnvelope.Records.map((record) => {

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

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { type MiddyLikeRequest } from '@aws-lambda-powertools/commons/types';
22
import { type MiddlewareObj } from '@middy/core';
3-
import { type ZodSchema } from 'zod';
4-
import { type ParserOptions } from '../types/parser.js';
3+
import { ZodType } from 'zod';
4+
import type { ParserOptions, ParserOutput } from '../types/parser.js';
55
import { parse } from '../parser.js';
6+
import type { Envelope } from '../types/envelope.js';
67

78
/**
89
* A middiy middleware to parse your event.
@@ -32,9 +33,13 @@ import { parse } from '../parser.js';
3233
*
3334
* @param options
3435
*/
35-
const parser = <S extends ZodSchema>(
36-
options: ParserOptions<S>
37-
): MiddlewareObj => {
36+
const parser = <
37+
TSchema extends ZodType,
38+
TEnvelope extends Envelope = undefined,
39+
TSafeParse extends boolean = false,
40+
>(
41+
options: ParserOptions<TSchema, TEnvelope, TSafeParse>
42+
): MiddlewareObj<ParserOutput<TSchema, TEnvelope, TSafeParse>> => {
3843
const before = (request: MiddyLikeRequest): void => {
3944
const { schema, envelope, safeParse } = options;
4045

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

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types';
22
import type { Context, Handler } from 'aws-lambda';
3-
import { ZodSchema, z } from 'zod';
3+
import { type ZodSchema } from 'zod';
44
import { parse } from './parser.js';
5-
import type { ParserOptions, ParsedResult } from './types/index.js';
5+
import type { ParserOptions, Envelope } from './types/index.js';
6+
import type { ParserOutput } from './types/parser.js';
67

78
/**
89
* A decorator to parse your event.
@@ -67,8 +68,12 @@ import type { ParserOptions, ParsedResult } from './types/index.js';
6768
*
6869
* @param options Configure the parser with the `schema`, `envelope` and whether to `safeParse` or not
6970
*/
70-
export const parser = <S extends ZodSchema>(
71-
options: ParserOptions<S>
71+
export const parser = <
72+
TSchema extends ZodSchema,
73+
TEnvelope extends Envelope = undefined,
74+
TSafeParse extends boolean = false,
75+
>(
76+
options: ParserOptions<TSchema, TEnvelope, TSafeParse>
7277
): HandlerMethodDecorator => {
7378
return (_target, _propertyKey, descriptor) => {
7479
const original = descriptor.value!;
@@ -77,14 +82,11 @@ export const parser = <S extends ZodSchema>(
7782

7883
descriptor.value = async function (
7984
this: Handler,
80-
event: unknown,
85+
event: ParserOutput<TSchema, TEnvelope, TSafeParse>,
8186
context: Context,
8287
callback
8388
) {
84-
const parsedEvent: ParsedResult<
85-
typeof event,
86-
z.infer<typeof schema>
87-
> = parse(event, envelope, schema, safeParse);
89+
const parsedEvent = parse(event, envelope, schema, safeParse);
8890

8991
return original.call(this, parsedEvent, context, callback);
9092
};

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

+27-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@ import type {
1414
VpcLatticeEnvelope,
1515
VpcLatticeV2Envelope,
1616
} from '../envelopes/index.js';
17+
import { z, type ZodSchema } from 'zod';
1718

18-
export type Envelope =
19+
type DynamoDBStreamEnvelopeResponse<Schema extends ZodSchema> = {
20+
NewImage: z.infer<Schema>;
21+
OldImage: z.infer<Schema>;
22+
};
23+
24+
type Envelope =
1925
| typeof ApiGatewayEnvelope
2026
| typeof ApiGatewayV2Envelope
2127
| typeof CloudWatchEnvelope
@@ -29,4 +35,23 @@ export type Envelope =
2935
| typeof SnsSqsEnvelope
3036
| typeof SqsEnvelope
3137
| typeof VpcLatticeEnvelope
32-
| typeof VpcLatticeV2Envelope;
38+
| typeof VpcLatticeV2Envelope
39+
| undefined;
40+
41+
/**
42+
* Envelopes that return an array, needed to narrow down the return type of the parser
43+
*/
44+
type EnvelopeArrayReturnType =
45+
| typeof CloudWatchEnvelope
46+
| typeof DynamoDBStreamEnvelope
47+
| typeof KafkaEnvelope
48+
| typeof KinesisEnvelope
49+
| typeof KinesisFirehoseEnvelope
50+
| typeof SnsEnvelope
51+
| typeof SqsEnvelope;
52+
53+
export type {
54+
Envelope,
55+
DynamoDBStreamEnvelopeResponse,
56+
EnvelopeArrayReturnType,
57+
};

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

+47-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
import type { ZodSchema, ZodError } from 'zod';
2-
import type { Envelope } from './envelope.js';
1+
import { type ZodSchema, type ZodError, z } from 'zod';
2+
import type { Envelope, EnvelopeArrayReturnType } from './envelope.js';
33

44
/**
55
* Options for the parser used in middy middleware and decorator
66
*/
7-
type ParserOptions<S extends ZodSchema> = {
8-
schema: S;
9-
envelope?: Envelope;
10-
safeParse?: boolean;
7+
type ParserOptions<
8+
TSchema extends ZodSchema,
9+
TEnvelope extends Envelope,
10+
TSafeParse extends boolean,
11+
> = {
12+
schema: TSchema;
13+
envelope?: TEnvelope;
14+
safeParse?: TSafeParse;
1115
};
1216

1317
/**
@@ -34,9 +38,46 @@ type ParsedResult<Input = unknown, Output = unknown> =
3438
| ParsedResultSuccess<Output>
3539
| ParsedResultError<Input>;
3640

41+
/**
42+
* The inferred result of the schema, can be either an array or a single object depending on the envelope
43+
*/
44+
type ZodInferredResult<
45+
TSchema extends ZodSchema,
46+
TEnvelope extends Envelope,
47+
> = undefined extends TEnvelope
48+
? z.infer<TSchema>
49+
: TEnvelope extends EnvelopeArrayReturnType
50+
? z.infer<TSchema>[]
51+
: z.infer<TSchema>;
52+
53+
type ZodInferredSafeParseResult<
54+
TSchema extends ZodSchema,
55+
TEnvelope extends Envelope,
56+
> = undefined extends TEnvelope
57+
? ParsedResult<unknown, z.infer<TSchema>>
58+
: TEnvelope extends EnvelopeArrayReturnType
59+
? ParsedResult<unknown, z.infer<TSchema>>
60+
: ParsedResult<unknown, z.infer<TSchema>[]>;
61+
62+
/**
63+
* The output of the parser function, can be either schema inferred type or a ParsedResult
64+
*/
65+
type ParserOutput<
66+
TSchema extends ZodSchema,
67+
TEnvelope extends Envelope,
68+
TSafeParse = false,
69+
> = undefined extends TSafeParse
70+
? ZodInferredResult<TSchema, TEnvelope>
71+
: TSafeParse extends true
72+
? ZodInferredSafeParseResult<TSchema, TEnvelope>
73+
: TSafeParse extends false
74+
? ZodInferredResult<TSchema, TEnvelope>
75+
: never;
76+
3777
export type {
3878
ParserOptions,
3979
ParsedResult,
4080
ParsedResultError,
4181
ParsedResultSuccess,
82+
ParserOutput,
4283
};

Diff for: packages/parser/tests/unit/parser.decorator.test.ts

+11-20
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('Parser Decorator', () => {
2727
public async handler(
2828
event: TestEvent,
2929
_context: Context
30-
): Promise<unknown> {
30+
): Promise<TestEvent> {
3131
return event;
3232
}
3333

@@ -60,7 +60,7 @@ describe('Parser Decorator', () => {
6060
safeParse: true,
6161
})
6262
public async handlerWithSchemaAndSafeParse(
63-
event: ParsedResult<TestEvent, TestEvent>,
63+
event: ParsedResult<unknown, TestEvent>,
6464
_context: Context
6565
): Promise<ParsedResult> {
6666
return event;
@@ -99,9 +99,7 @@ describe('Parser Decorator', () => {
9999
testEvent.detail = customPayload;
100100

101101
const resp = await lambda.handlerWithSchemaAndEnvelope(
102-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
103-
// @ts-ignore
104-
testEvent,
102+
testEvent as unknown as TestEvent,
105103
{} as Context
106104
);
107105

@@ -130,9 +128,7 @@ describe('Parser Decorator', () => {
130128
testEvent.detail = customPayload;
131129

132130
const resp = await lambda.handlerWithParserCallsAnotherMethod(
133-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
134-
// @ts-ignore
135-
testEvent,
131+
testEvent as unknown as TestEvent,
136132
{} as Context
137133
);
138134

@@ -143,9 +139,7 @@ describe('Parser Decorator', () => {
143139
const testEvent = generateMock(TestSchema);
144140

145141
const resp = await lambda.handlerWithSchemaAndSafeParse(
146-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
147-
// @ts-ignore
148-
testEvent,
142+
testEvent as unknown as ParsedResult<unknown, TestEvent>,
149143
{} as Context
150144
);
151145

@@ -157,9 +151,10 @@ describe('Parser Decorator', () => {
157151

158152
it('should parse event with schema and safeParse and return error', async () => {
159153
expect(
160-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
161-
// @ts-ignore
162-
await lambda.handlerWithSchemaAndSafeParse({ foo: 'bar' }, {} as Context)
154+
await lambda.handlerWithSchemaAndSafeParse(
155+
{ foo: 'bar' } as unknown as ParsedResult<unknown, TestEvent>,
156+
{} as Context
157+
)
163158
).toEqual({
164159
error: expect.any(ParseError),
165160
success: false,
@@ -173,9 +168,7 @@ describe('Parser Decorator', () => {
173168
event.detail = testEvent;
174169

175170
const resp = await lambda.harndlerWithEnvelopeAndSafeParse(
176-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
177-
// @ts-ignore
178-
event,
171+
event as unknown as ParsedResult<TestEvent, TestEvent>,
179172
{} as Context
180173
);
181174

@@ -188,9 +181,7 @@ describe('Parser Decorator', () => {
188181
it('should parse event with envelope and safeParse and return error', async () => {
189182
expect(
190183
await lambda.harndlerWithEnvelopeAndSafeParse(
191-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
192-
// @ts-ignore
193-
{ foo: 'bar' },
184+
{ foo: 'bar' } as unknown as ParsedResult<TestEvent, TestEvent>,
194185
{} as Context
195186
)
196187
).toEqual({

0 commit comments

Comments
 (0)