Skip to content

Commit e0de20b

Browse files
am29ddreamorosi
andauthored
docs(parser): add docs for parser utility (#1835)
* WIP: parser * fix test imports * remove unnecessary exports * add custom validation * remove unnecessary export * add warning * remove duplicate imports * add types and error handlig * remove comment from annotations * minor changes * revert merge changes * merged package-lock * Update docs/utilities/parser.md Co-authored-by: Andrea Amorosi <[email protected]> * Update docs/utilities/parser.md Co-authored-by: Andrea Amorosi <[email protected]> * adjust imports to new implementation * add safeParse * fixed line highlight * typo * revert index.md, add private scope to snippets packagef * Update docs/utilities/parser.md Co-authored-by: Andrea Amorosi <[email protected]> * add parser to main, fixed zod install command * fix callout indent * fix tooltip --------- Co-authored-by: Andrea Amorosi <[email protected]>
1 parent badfef5 commit e0de20b

20 files changed

+718
-10
lines changed

docs/index.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ You can include Powertools for AWS Lambda (TypeScript) Lambda Layer using [AWS L
7070
| `il-central-1` | [arn:aws:lambda:il-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:3](#){: .copyMe}:clipboard: |
7171

7272
??? note "Click to expand and copy code snippets for popular frameworks"
73-
73+
7474
=== "SAM"
7575

7676
```yaml hl_lines="5"
@@ -252,7 +252,7 @@ You can include Powertools for AWS Lambda (TypeScript) Lambda Layer using [AWS L
252252
!!! info "Using Powertools for AWS Lambda (TypeScript) via Lambda Layer? Simply add the Powertools for AWS Lambda (TypeScript) utilities you are using as a development dependency"
253253

254254
??? question "Want to inspect the contents of the Layer?"
255-
Change {region} to your AWS region, e.g. `eu-west-1`
255+
Change {region} to your AWS region, e.g. `eu-west-1`
256256

257257
```bash title="AWS CLI"
258258
aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{aws::region}:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:3 --region {region}
@@ -262,7 +262,7 @@ You can include Powertools for AWS Lambda (TypeScript) Lambda Layer using [AWS L
262262

263263
## Instrumentation
264264

265-
You can instrument your code with Powertools for AWS Lambda (TypeScript) in three different ways:
265+
You can instrument your code with Powertools for AWS Lambda (TypeScript) in three different ways:
266266

267267
* **Middy** middleware. It is the best choice if your existing code base relies on the [Middy 4.x](https://middy.js.org/docs/) middleware engine. Powertools for AWS Lambda (TypeScript) offers compatible Middy middleware to make this integration seamless.
268268
* **Method decorator**. Use [TypeScript method decorators](https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators) if you prefer writing your business logic using [TypeScript Classes](https://www.typescriptlang.org/docs/handbook/classes.html). If you aren’t using Classes, this requires the most significant refactoring.
@@ -289,11 +289,12 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al
289289
| [Parameters](./utilities/parameters.md) | High-level functions to retrieve one or more parameters from AWS SSM Parameter Store, AWS Secrets Manager, AWS AppConfig, and Amazon DynamoDB |
290290
| [Idempotency](./utilities/idempotency.md) | Class method decorator, Middy middleware, and function wrapper to make your Lambda functions idempotent and prevent duplicate execution based on payload content. |
291291
| [Batch Processing](./utilities/batch.md) | Utility to handle partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. |
292+
| [Parser](./utilities/parser.md) | Utility to parse and validate AWS Lambda event payloads using Zod, a TypeScript-first schema declaration and validation library. |
292293

293294
## Environment variables
294295

295296
???+ info
296-
Explicit parameters take precedence over environment variables
297+
Explicit parameters take precedence over environment variables
297298

298299
| Environment variable | Description | Utility | Default |
299300
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ------------------- |
@@ -351,4 +352,4 @@ These are our core principles to guide our decision making.
351352
* **We strive for backwards compatibility**. New features and changes should keep backwards compatibility. If a breaking change cannot be avoided, the deprecation and migration process should be clearly defined.
352353
* **We work backwards from the community**. We aim to strike a balance of what would work best for 80% of customers. Emerging practices are considered and discussed via Requests for Comment (RFCs)
353354
* **Progressive**. Utilities are designed to be incrementally adoptable for customers at any stage of their Serverless journey. They follow language idioms and their community’s common practices.
354-
355+

docs/snippets/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "docs",
33
"version": "2.0.3",
44
"description": "A collection code snippets for the Powertools for AWS Lambda (TypeScript) docs",
5+
"type": "module",
56
"author": {
67
"name": "Amazon Web Services",
78
"url": "https://aws.amazon.com"
@@ -34,10 +35,12 @@
3435
"@aws-sdk/client-secrets-manager": "^3.543.0",
3536
"@aws-sdk/client-ssm": "^3.540.0",
3637
"@aws-sdk/util-dynamodb": "^3.540.0",
38+
"@middy/core": "^4.7.0",
3739
"aws-sdk": "^2.1589.0",
3840
"aws-sdk-client-mock": "^4.0.0",
3941
"aws-sdk-client-mock-jest": "^4.0.0",
4042
"axios": "^1.6.8",
41-
"hashi-vault-js": "^0.4.14"
43+
"hashi-vault-js": "^0.4.14",
44+
"zod": "^3.22.4"
4245
}
4346
}

docs/snippets/parser/decorator.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Context } from 'aws-lambda';
2+
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
3+
import { parser } from '@aws-lambda-powertools/parser';
4+
import { z } from 'zod';
5+
import { Logger } from '@aws-lambda-powertools/logger';
6+
7+
const logger = new Logger();
8+
9+
const orderSchema = z.object({
10+
id: z.number().positive(),
11+
description: z.string(),
12+
items: z.array(
13+
z.object({
14+
id: z.number().positive(),
15+
quantity: z.number(),
16+
description: z.string(),
17+
})
18+
),
19+
optionalField: z.string().optional(),
20+
});
21+
22+
type Order = z.infer<typeof orderSchema>;
23+
24+
class Lambda implements LambdaInterface {
25+
@parser({ schema: orderSchema })
26+
public async handler(event: Order, _context: Context): Promise<void> {
27+
// event is now typed as Order
28+
for (const item of event.items) {
29+
logger.info('Processing item', { item });
30+
}
31+
}
32+
}
33+
34+
const myFunction = new Lambda();
35+
export const handler = myFunction.handler.bind(myFunction);
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Context } from 'aws-lambda';
2+
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
3+
import { parser } from '@aws-lambda-powertools/parser';
4+
import { z } from 'zod';
5+
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
8+
const logger = new Logger();
9+
10+
const orderSchema = z.object({
11+
id: z.number().positive(),
12+
description: z.string(),
13+
items: z.array(
14+
z.object({
15+
id: z.number().positive(),
16+
quantity: z.number(),
17+
description: z.string(),
18+
})
19+
),
20+
optionalField: z.string().optional(),
21+
});
22+
23+
type Order = z.infer<typeof orderSchema>;
24+
25+
class Lambda implements LambdaInterface {
26+
@parser({ schema: orderSchema, envelope: EventBridgeEnvelope }) // (1)!
27+
public async handler(event: Order, _context: Context): Promise<void> {
28+
// event is now typed as Order
29+
for (const item of event.items) {
30+
logger.info('Processing item', item); // (2)!
31+
}
32+
}
33+
}
34+
35+
const myFunction = new Lambda();
36+
export const handler = myFunction.handler.bind(myFunction);

docs/snippets/parser/envelopeMiddy.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Context } from 'aws-lambda';
2+
import { parser } from '@aws-lambda-powertools/parser/middleware';
3+
import { z } from 'zod';
4+
import middy from '@middy/core';
5+
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
8+
const logger = new Logger();
9+
10+
const orderSchema = z.object({
11+
id: z.number().positive(),
12+
description: z.string(),
13+
items: z.array(
14+
z.object({
15+
id: z.number().positive(),
16+
quantity: z.number(),
17+
description: z.string(),
18+
})
19+
),
20+
optionalField: z.string().optional(),
21+
});
22+
23+
type Order = z.infer<typeof orderSchema>;
24+
25+
const lambdaHandler = async (
26+
event: Order,
27+
_context: Context
28+
): Promise<void> => {
29+
for (const item of event.items) {
30+
// item is parsed as OrderItem
31+
logger.info('Processing item', { item });
32+
}
33+
};
34+
35+
export const handler = middy(lambdaHandler).use(
36+
parser({ schema: orderSchema, envelope: EventBridgeEnvelope })
37+
);
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"version": "0",
3+
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
4+
"detail-type": "OrderPurchased",
5+
"source": "OrderService",
6+
"account": "111122223333",
7+
"time": "2020-10-22T18:43:48Z",
8+
"region": "us-west-1",
9+
"resources": ["some_additional"],
10+
"detail": {
11+
"id": 10876546789,
12+
"description": "My order",
13+
"items": [
14+
{
15+
"id": 1015938732,
16+
"quantity": 1,
17+
"description": "item xpto"
18+
}
19+
]
20+
}
21+
}

docs/snippets/parser/extend.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Context } from 'aws-lambda';
2+
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
3+
import { parser } from '@aws-lambda-powertools/parser';
4+
import { z } from 'zod';
5+
import { EventBridgeSchema } from '@aws-lambda-powertools/parser/schemas';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
8+
const logger = new Logger();
9+
10+
const orderSchema = z.object({
11+
id: z.number().positive(),
12+
description: z.string(),
13+
items: z.array(
14+
z.object({
15+
id: z.number().positive(),
16+
quantity: z.number(),
17+
description: z.string(),
18+
})
19+
),
20+
optionalField: z.string().optional(),
21+
});
22+
23+
const orderEventSchema = EventBridgeSchema.extend({
24+
detail: orderSchema, // (1)!
25+
});
26+
27+
type OrderEvent = z.infer<typeof orderEventSchema>;
28+
29+
class Lambda implements LambdaInterface {
30+
@parser({ schema: orderEventSchema }) // (2)!
31+
public async handler(event: OrderEvent, _context: Context): Promise<void> {
32+
for (const item of event.detail.items) {
33+
// process OrderItem
34+
logger.info('Processing item', { item }); // (3)!
35+
}
36+
}
37+
}
38+
39+
const myFunction = new Lambda();
40+
export const handler = myFunction.handler.bind(myFunction);

docs/snippets/parser/manual.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Context } from 'aws-lambda';
2+
import { z } from 'zod';
3+
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
4+
import { EventBridgeSchema } from '@aws-lambda-powertools/parser/schemas';
5+
import type { EventBridgeEvent } from '@aws-lambda-powertools/parser/types';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
8+
const logger = new Logger();
9+
10+
const orderSchema = z.object({
11+
id: z.number().positive(),
12+
description: z.string(),
13+
items: z.array(
14+
z.object({
15+
id: z.number().positive(),
16+
quantity: z.number(),
17+
description: z.string(),
18+
})
19+
),
20+
optionalField: z.string().optional(),
21+
});
22+
type Order = z.infer<typeof orderSchema>;
23+
24+
export const handler = async (
25+
event: EventBridgeEvent,
26+
_context: Context
27+
): Promise<void> => {
28+
const parsedEvent = EventBridgeSchema.parse(event); // (1)!
29+
logger.info('Parsed event', parsedEvent);
30+
31+
const orders: Order = EventBridgeEnvelope.parse(event, orderSchema); // (2)!
32+
logger.info('Parsed orders', orders);
33+
};
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Context } from 'aws-lambda';
2+
import { z } from 'zod';
3+
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
4+
import { EventBridgeSchema } from '@aws-lambda-powertools/parser/schemas';
5+
import type { EventBridgeEvent } from '@aws-lambda-powertools/parser/types';
6+
import { Logger } from '@aws-lambda-powertools/logger';
7+
8+
const logger = new Logger();
9+
10+
const orderSchema = z.object({
11+
id: z.number().positive(),
12+
description: z.string(),
13+
items: z.array(
14+
z.object({
15+
id: z.number().positive(),
16+
quantity: z.number(),
17+
description: z.string(),
18+
})
19+
),
20+
optionalField: z.string().optional(),
21+
});
22+
23+
export const handler = async (
24+
event: EventBridgeEvent,
25+
_context: Context
26+
): Promise<void> => {
27+
const parsedEvent = EventBridgeSchema.safeParse(event); // (1)!
28+
parsedEvent.success
29+
? logger.info('Event parsed successfully', parsedEvent.data)
30+
: logger.error('Event parsing failed', parsedEvent.error);
31+
const parsedEvenlope = EventBridgeEnvelope.safeParse(event, orderSchema); // (2)!
32+
parsedEvenlope.success
33+
? logger.info('Event envelope parsed successfully', parsedEvenlope.data)
34+
: logger.error('Event envelope parsing failed', parsedEvenlope.error);
35+
};

docs/snippets/parser/middy.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { Context } from 'aws-lambda';
2+
import { parser } from '@aws-lambda-powertools/parser/middleware';
3+
import { z } from 'zod';
4+
import middy from '@middy/core';
5+
import { Logger } from '@aws-lambda-powertools/logger';
6+
7+
const logger = new Logger();
8+
9+
const orderSchema = z.object({
10+
id: z.number().positive(),
11+
description: z.string(),
12+
items: z.array(
13+
z.object({
14+
id: z.number().positive(),
15+
quantity: z.number(),
16+
description: z.string(),
17+
})
18+
),
19+
optionalField: z.string().optional(),
20+
});
21+
22+
type Order = z.infer<typeof orderSchema>;
23+
24+
const lambdaHandler = async (
25+
event: Order,
26+
_context: Context
27+
): Promise<void> => {
28+
for (const item of event.items) {
29+
// item is parsed as OrderItem
30+
logger.info('Processing item', { item });
31+
}
32+
};
33+
34+
export const handler = middy(lambdaHandler).use(
35+
parser({ schema: orderSchema })
36+
);

docs/snippets/parser/refine.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { z } from 'zod';
2+
3+
const orderItemSchema = z.object({
4+
id: z.number().positive(),
5+
quantity: z.number(),
6+
description: z.string(),
7+
});
8+
9+
export const orderSchema = z
10+
.object({
11+
id: z.number().positive(),
12+
description: z.string(),
13+
items: z.array(orderItemSchema).refine((items) => items.length > 0, {
14+
message: 'Order must have at least one item', // (1)!
15+
}),
16+
optionalField: z.string().optional(),
17+
})
18+
.refine((order) => order.id > 100 && order.items.length > 100, {
19+
message:
20+
'All orders with more than 100 items must have an id greater than 100', // (2)!
21+
});

0 commit comments

Comments
 (0)