-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(parser): implement parser decorator (#1831)
- Loading branch information
Showing
5 changed files
with
187 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types'; | ||
import { Context, Handler } from 'aws-lambda'; | ||
import { ZodSchema } from 'zod'; | ||
import { type ParserOptions } from './types/ParserOptions.js'; | ||
|
||
/** | ||
* A decorator to parse your event. | ||
* | ||
* @example | ||
* ```typescript | ||
* | ||
* import { parser } from '@aws-lambda-powertools/parser'; | ||
* import { sqsEnvelope } from '@aws-lambda-powertools/parser/envelopes/sqs'; | ||
* | ||
* | ||
* const Order = z.object({ | ||
* orderId: z.string(), | ||
* description: z.string(), | ||
* } | ||
* | ||
* class Lambda extends LambdaInterface { | ||
* | ||
* @parser({ envelope: sqsEnvelope, schema: OrderSchema }) | ||
* public async handler(event: Order, _context: Context): Promise<unknown> { | ||
* // sqs event is parsed and the payload is extracted and parsed | ||
* // apply business logic to your Order event | ||
* const res = processOrder(event); | ||
* return res; | ||
* } | ||
* } | ||
* | ||
* @param options | ||
*/ | ||
const parser = <S extends ZodSchema>( | ||
options: ParserOptions<S> | ||
): HandlerMethodDecorator => { | ||
return (_target, _propertyKey, descriptor) => { | ||
const original = descriptor.value!; | ||
|
||
const { schema, envelope } = options; | ||
|
||
descriptor.value = async function ( | ||
this: Handler, | ||
event: unknown, | ||
context: Context, | ||
callback | ||
) { | ||
const parsedEvent = envelope | ||
? envelope(event, schema) | ||
: schema.parse(event); | ||
|
||
return original.call(this, parsedEvent, context, callback); | ||
}; | ||
|
||
return descriptor; | ||
}; | ||
}; | ||
|
||
export { parser }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type { ZodSchema } from 'zod'; | ||
import { Envelope } from './envelope.js'; | ||
|
||
type ParserOptions<S extends ZodSchema> = { | ||
schema: S; | ||
envelope?: Envelope; | ||
}; | ||
|
||
export { type ParserOptions }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/** | ||
* Test decorator parser | ||
* | ||
* @group unit/parser | ||
*/ | ||
|
||
import { LambdaInterface } from '@aws-lambda-powertools/commons/lib/esm/types'; | ||
import { Context, EventBridgeEvent } from 'aws-lambda'; | ||
import { parser } from '../../src/parser'; | ||
import { TestSchema, TestEvents } from './schema/utils'; | ||
import { generateMock } from '@anatine/zod-mock'; | ||
import { eventBridgeEnvelope } from '../../src/envelopes/event-bridge'; | ||
import { EventBridgeSchema } from '../../src/schemas/eventbridge'; | ||
import { z } from 'zod'; | ||
|
||
describe('Parser Decorator', () => { | ||
const customEventBridgeSchema = EventBridgeSchema.extend({ | ||
detail: TestSchema, | ||
}); | ||
|
||
type TestSchema = z.infer<typeof TestSchema>; | ||
|
||
class TestClass implements LambdaInterface { | ||
@parser({ schema: TestSchema }) | ||
public async handler( | ||
event: TestSchema, | ||
_context: Context | ||
): Promise<unknown> { | ||
return event; | ||
} | ||
|
||
@parser({ schema: customEventBridgeSchema }) | ||
public async handlerWithCustomSchema( | ||
event: unknown, | ||
_context: Context | ||
): Promise<unknown> { | ||
return event; | ||
} | ||
|
||
@parser({ schema: TestSchema, envelope: eventBridgeEnvelope }) | ||
public async handlerWithParserCallsAnotherMethod( | ||
event: unknown, | ||
_context: Context | ||
): Promise<unknown> { | ||
return this.anotherMethod(event as TestSchema); | ||
} | ||
|
||
@parser({ envelope: eventBridgeEnvelope, schema: TestSchema }) | ||
public async handlerWithSchemaAndEnvelope( | ||
event: unknown, | ||
_context: Context | ||
): Promise<unknown> { | ||
return event; | ||
} | ||
|
||
private async anotherMethod(event: TestSchema): Promise<TestSchema> { | ||
return event; | ||
} | ||
} | ||
|
||
const lambda = new TestClass(); | ||
|
||
it('should parse custom schema event', async () => { | ||
const testEvent = generateMock(TestSchema); | ||
|
||
const resp = await lambda.handler(testEvent, {} as Context); | ||
|
||
expect(resp).toEqual(testEvent); | ||
}); | ||
|
||
it('should parse custom schema with envelope event', async () => { | ||
const customPayload = generateMock(TestSchema); | ||
const testEvent = TestEvents.eventBridgeEvent as EventBridgeEvent< | ||
string, | ||
unknown | ||
>; | ||
testEvent.detail = customPayload; | ||
|
||
const resp = await lambda.handlerWithSchemaAndEnvelope( | ||
testEvent, | ||
{} as Context | ||
); | ||
|
||
expect(resp).toEqual(customPayload); | ||
}); | ||
|
||
it('should parse extended envelope event', async () => { | ||
const customPayload = generateMock(TestSchema); | ||
|
||
const testEvent = generateMock(customEventBridgeSchema); | ||
testEvent.detail = customPayload; | ||
|
||
const resp: z.infer<typeof customEventBridgeSchema> = | ||
(await lambda.handlerWithCustomSchema( | ||
testEvent, | ||
{} as Context | ||
)) as z.infer<typeof customEventBridgeSchema>; | ||
|
||
expect(customEventBridgeSchema.parse(resp)).toEqual(testEvent); | ||
expect(resp.detail).toEqual(customPayload); | ||
}); | ||
|
||
it('should parse and call private async method', async () => { | ||
const customPayload = generateMock(TestSchema); | ||
const testEvent = TestEvents.eventBridgeEvent as EventBridgeEvent< | ||
string, | ||
unknown | ||
>; | ||
testEvent.detail = customPayload; | ||
|
||
const resp = await lambda.handlerWithParserCallsAnotherMethod( | ||
testEvent, | ||
{} as Context | ||
); | ||
|
||
expect(resp).toEqual(customPayload); | ||
}); | ||
}); |
File renamed without changes.