Skip to content

Commit

Permalink
feat(parser): implement parser decorator (#1831)
Browse files Browse the repository at this point in the history
  • Loading branch information
am29d authored Dec 21, 2023
1 parent 803f8d9 commit 5343894
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 6 deletions.
7 changes: 1 addition & 6 deletions packages/parser/src/middleware/parser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { type MiddyLikeRequest } from '@aws-lambda-powertools/commons/types';
import { type MiddlewareObj } from '@middy/core';
import { type ZodSchema } from 'zod';
import { type Envelope } from '../types/envelope.js';

interface ParserOptions<S extends ZodSchema> {
schema: S;
envelope?: Envelope;
}
import { type ParserOptions } from '../types/ParserOptions.js';

/**
* A middiy middleware to parse your event.
Expand Down
59 changes: 59 additions & 0 deletions packages/parser/src/parser.ts
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 };
9 changes: 9 additions & 0 deletions packages/parser/src/types/ParserOptions.ts
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 };
118 changes: 118 additions & 0 deletions packages/parser/tests/unit/parser.decorator.test.ts
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.

0 comments on commit 5343894

Please sign in to comment.