Skip to content

Commit 5343894

Browse files
authored
feat(parser): implement parser decorator (#1831)
1 parent 803f8d9 commit 5343894

File tree

5 files changed

+187
-6
lines changed

5 files changed

+187
-6
lines changed

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

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import { type MiddyLikeRequest } from '@aws-lambda-powertools/commons/types';
22
import { type MiddlewareObj } from '@middy/core';
33
import { type ZodSchema } from 'zod';
4-
import { type Envelope } from '../types/envelope.js';
5-
6-
interface ParserOptions<S extends ZodSchema> {
7-
schema: S;
8-
envelope?: Envelope;
9-
}
4+
import { type ParserOptions } from '../types/ParserOptions.js';
105

116
/**
127
* A middiy middleware to parse your event.

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

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types';
2+
import { Context, Handler } from 'aws-lambda';
3+
import { ZodSchema } from 'zod';
4+
import { type ParserOptions } from './types/ParserOptions.js';
5+
6+
/**
7+
* A decorator to parse your event.
8+
*
9+
* @example
10+
* ```typescript
11+
*
12+
* import { parser } from '@aws-lambda-powertools/parser';
13+
* import { sqsEnvelope } from '@aws-lambda-powertools/parser/envelopes/sqs';
14+
*
15+
*
16+
* const Order = z.object({
17+
* orderId: z.string(),
18+
* description: z.string(),
19+
* }
20+
*
21+
* class Lambda extends LambdaInterface {
22+
*
23+
* @parser({ envelope: sqsEnvelope, schema: OrderSchema })
24+
* public async handler(event: Order, _context: Context): Promise<unknown> {
25+
* // sqs event is parsed and the payload is extracted and parsed
26+
* // apply business logic to your Order event
27+
* const res = processOrder(event);
28+
* return res;
29+
* }
30+
* }
31+
*
32+
* @param options
33+
*/
34+
const parser = <S extends ZodSchema>(
35+
options: ParserOptions<S>
36+
): HandlerMethodDecorator => {
37+
return (_target, _propertyKey, descriptor) => {
38+
const original = descriptor.value!;
39+
40+
const { schema, envelope } = options;
41+
42+
descriptor.value = async function (
43+
this: Handler,
44+
event: unknown,
45+
context: Context,
46+
callback
47+
) {
48+
const parsedEvent = envelope
49+
? envelope(event, schema)
50+
: schema.parse(event);
51+
52+
return original.call(this, parsedEvent, context, callback);
53+
};
54+
55+
return descriptor;
56+
};
57+
};
58+
59+
export { parser };

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

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { ZodSchema } from 'zod';
2+
import { Envelope } from './envelope.js';
3+
4+
type ParserOptions<S extends ZodSchema> = {
5+
schema: S;
6+
envelope?: Envelope;
7+
};
8+
9+
export { type ParserOptions };

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

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* Test decorator parser
3+
*
4+
* @group unit/parser
5+
*/
6+
7+
import { LambdaInterface } from '@aws-lambda-powertools/commons/lib/esm/types';
8+
import { Context, EventBridgeEvent } from 'aws-lambda';
9+
import { parser } from '../../src/parser';
10+
import { TestSchema, TestEvents } from './schema/utils';
11+
import { generateMock } from '@anatine/zod-mock';
12+
import { eventBridgeEnvelope } from '../../src/envelopes/event-bridge';
13+
import { EventBridgeSchema } from '../../src/schemas/eventbridge';
14+
import { z } from 'zod';
15+
16+
describe('Parser Decorator', () => {
17+
const customEventBridgeSchema = EventBridgeSchema.extend({
18+
detail: TestSchema,
19+
});
20+
21+
type TestSchema = z.infer<typeof TestSchema>;
22+
23+
class TestClass implements LambdaInterface {
24+
@parser({ schema: TestSchema })
25+
public async handler(
26+
event: TestSchema,
27+
_context: Context
28+
): Promise<unknown> {
29+
return event;
30+
}
31+
32+
@parser({ schema: customEventBridgeSchema })
33+
public async handlerWithCustomSchema(
34+
event: unknown,
35+
_context: Context
36+
): Promise<unknown> {
37+
return event;
38+
}
39+
40+
@parser({ schema: TestSchema, envelope: eventBridgeEnvelope })
41+
public async handlerWithParserCallsAnotherMethod(
42+
event: unknown,
43+
_context: Context
44+
): Promise<unknown> {
45+
return this.anotherMethod(event as TestSchema);
46+
}
47+
48+
@parser({ envelope: eventBridgeEnvelope, schema: TestSchema })
49+
public async handlerWithSchemaAndEnvelope(
50+
event: unknown,
51+
_context: Context
52+
): Promise<unknown> {
53+
return event;
54+
}
55+
56+
private async anotherMethod(event: TestSchema): Promise<TestSchema> {
57+
return event;
58+
}
59+
}
60+
61+
const lambda = new TestClass();
62+
63+
it('should parse custom schema event', async () => {
64+
const testEvent = generateMock(TestSchema);
65+
66+
const resp = await lambda.handler(testEvent, {} as Context);
67+
68+
expect(resp).toEqual(testEvent);
69+
});
70+
71+
it('should parse custom schema with envelope event', async () => {
72+
const customPayload = generateMock(TestSchema);
73+
const testEvent = TestEvents.eventBridgeEvent as EventBridgeEvent<
74+
string,
75+
unknown
76+
>;
77+
testEvent.detail = customPayload;
78+
79+
const resp = await lambda.handlerWithSchemaAndEnvelope(
80+
testEvent,
81+
{} as Context
82+
);
83+
84+
expect(resp).toEqual(customPayload);
85+
});
86+
87+
it('should parse extended envelope event', async () => {
88+
const customPayload = generateMock(TestSchema);
89+
90+
const testEvent = generateMock(customEventBridgeSchema);
91+
testEvent.detail = customPayload;
92+
93+
const resp: z.infer<typeof customEventBridgeSchema> =
94+
(await lambda.handlerWithCustomSchema(
95+
testEvent,
96+
{} as Context
97+
)) as z.infer<typeof customEventBridgeSchema>;
98+
99+
expect(customEventBridgeSchema.parse(resp)).toEqual(testEvent);
100+
expect(resp.detail).toEqual(customPayload);
101+
});
102+
103+
it('should parse and call private async method', async () => {
104+
const customPayload = generateMock(TestSchema);
105+
const testEvent = TestEvents.eventBridgeEvent as EventBridgeEvent<
106+
string,
107+
unknown
108+
>;
109+
testEvent.detail = customPayload;
110+
111+
const resp = await lambda.handlerWithParserCallsAnotherMethod(
112+
testEvent,
113+
{} as Context
114+
);
115+
116+
expect(resp).toEqual(customPayload);
117+
});
118+
});

0 commit comments

Comments
 (0)