Skip to content

Commit 0caf05a

Browse files
committed
add safeParse
1 parent 8bede3e commit 0caf05a

13 files changed

+197
-28
lines changed

docs/snippets/package.json

Lines changed: 2 additions & 0 deletions
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"
@@ -33,6 +34,7 @@
3334
"@aws-sdk/client-secrets-manager": "^3.543.0",
3435
"@aws-sdk/client-ssm": "^3.540.0",
3536
"@aws-sdk/util-dynamodb": "^3.540.0",
37+
"@middy/core": "^4.7.0",
3638
"aws-sdk": "^2.1589.0",
3739
"aws-sdk-client-mock": "^4.0.0",
3840
"aws-sdk-client-mock-jest": "^4.0.0",

docs/snippets/parser/decorator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type Order = z.infer<typeof orderSchema>;
2121
class Lambda implements LambdaInterface {
2222
@parser({ schema: orderSchema })
2323
public async handler(event: Order, _context: Context): Promise<void> {
24+
// event is now typed as Order
2425
for (const item of event.items) {
25-
// item is parsed as OrderItem
2626
console.log(item.id);
2727
}
2828
}

docs/snippets/parser/envelopeDecorator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ type Order = z.infer<typeof orderSchema>;
2222
class Lambda implements LambdaInterface {
2323
@parser({ schema: orderSchema, envelope: EventBridgeEnvelope }) // (1)!
2424
public async handler(event: Order, _context: Context): Promise<void> {
25+
// event is now typed as Order
2526
for (const item of event.items) {
26-
// item is parsed as OrderItem
2727
console.log(item.id); // (2)!
2828
}
2929
}

docs/snippets/parser/extend.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Context } from 'aws-lambda';
1+
import type { Context } from 'aws-lambda';
22
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
33
import { parser } from '@aws-lambda-powertools/parser';
44
import { z } from 'zod';
Lines changed: 33 additions & 0 deletions
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+
7+
const orderSchema = z.object({
8+
id: z.number().positive(),
9+
description: z.string(),
10+
items: z.array(
11+
z.object({
12+
id: z.number().positive(),
13+
quantity: z.number(),
14+
description: z.string(),
15+
})
16+
),
17+
optionalField: z.string().optional(),
18+
});
19+
20+
export const handler = async (
21+
event: EventBridgeEvent,
22+
_context: Context
23+
): Promise<void> => {
24+
const parsedEvent = EventBridgeSchema.safeParse(event); // (1)!
25+
parsedEvent.success
26+
? console.log(parsedEvent.data)
27+
: console.log(parsedEvent.error.message);
28+
29+
const parsedEvenlope = EventBridgeEnvelope.safeParse(event, orderSchema); // (2)!
30+
parsedEvenlope.success
31+
? console.log(parsedEvenlope.data)
32+
: console.log(parsedEvenlope.error.message);
33+
};

docs/snippets/parser/middy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Context } from 'aws-lambda';
1+
import type { Context } from 'aws-lambda';
22
import { parser } from '@aws-lambda-powertools/parser/middleware';
33
import { z } from 'zod';
44
import middy from '@middy/core';
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 type {
6+
ParsedResult,
7+
EventBridgeEvent,
8+
} from '@aws-lambda-powertools/parser/types';
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, safeParse: true }) // (1)!
27+
public async handler(
28+
event: ParsedResult<EventBridgeEvent, Order>,
29+
_context: Context
30+
): Promise<void> {
31+
if (event.success) {
32+
// (2)!
33+
for (const item of event.data.items) {
34+
console.log(item.id); // (3)!
35+
}
36+
} else {
37+
console.error(event.error); // (4)!
38+
console.log(event.originalEvent); // (5)!
39+
}
40+
}
41+
}
42+
43+
const myFunction = new Lambda();
44+
export const handler = myFunction.handler.bind(myFunction);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 type {
6+
ParsedResult,
7+
EventBridgeEvent,
8+
} from '@aws-lambda-powertools/parser/types';
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: ParsedResult<EventBridgeEvent, Order>,
27+
_context: Context
28+
): Promise<void> => {
29+
if (event.success) {
30+
// (2)!
31+
for (const item of event.data.items) {
32+
console.log(item.id); // (3)!
33+
}
34+
} else {
35+
console.error(event.error); // (4)!
36+
console.log(event.originalEvent); // (5)!
37+
}
38+
};
39+
40+
export const handler = middy(lambdaHandler).use(
41+
parser({ schema: orderSchema, safeParse: true }) // (1)!
42+
);

docs/snippets/parser/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Context } from 'aws-lambda';
1+
import type { Context } from 'aws-lambda';
22
import { parser } from '@aws-lambda-powertools/parser/middleware';
33
import { z } from 'zod';
44
import middy from '@middy/core';

docs/snippets/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"@aws-lambda-powertools/jmespath": ["../../packages/jmespath/lib"],
3434
"@aws-lambda-powertools/jmespath/envelopes": [
3535
"../../packages/jmespath/lib/envelopes"
36-
]
36+
],
37+
"@aws-lambda-powertools/parser": ["../../packages/parser/lib"]
3738
}
3839
}
3940
}

docs/utilities/parser.md

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,56 @@
11
---
22
title: Parser (Zod)
33
descrition: Utility
4+
status: new
45
---
56

67

78
???+ warning
89
**This utility is currently released as beta developer preview** and is intended strictly for feedback and testing purposes **and not for production workloads**. The version and all future versions tagged with the `-beta` suffix should be treated as not stable. Up until before the [General Availability release](https://github.com/aws-powertools/powertools-lambda-typescript/milestone/16) we might introduce significant breaking changes and improvements in response to customers feedback.
910

10-
This utility provides data validation and parsing using [zod](https://zod.dev){target="_blank"}.
11+
This utility provides data validation and parsing using [Zod](https://zod.dev){target="_blank"}.
1112
Zod is a TypeScript-first schema declaration and validation library.
1213

1314
## Key features
1415

15-
* Define data schema as zod schema, then parse, validate and extract only what you want
16-
* Built-in envelopes to unwrap and validate popular event sources payloads
16+
* Define data schema as Zod schema, then parse, validate and extract only what you want
17+
* Built-in envelopes to unwrap and validate popular AWS event sources payloads
1718
* Extend and customize envelopes to fit your needs
19+
* Safe parsing option to avoid throwing errors and custom error handling
1820
* Available for Middy.js middleware and TypeScript method decorators
1921

2022
## Getting started
2123

2224
### Install
2325

2426
```bash
25-
npm install @aws-lambda-powertools/parser zod
27+
npm install @aws-lambda-powertools/parser zod~3
2628
```
2729

28-
This utility supports zod v3.0.0 and above.
30+
This utility supports Zod v3.x and above.
2931

3032
## Define schema
3133

32-
You can define your schema using zod:
34+
You can define your schema using Zod:
3335

3436
```typescript title="schema.ts"
3537
--8<-- "docs/snippets/parser/schema.ts"
3638
```
3739

38-
This is a schema for order and order items using zod.
39-
You can create more complex schemas using zod, such as nested objects, arrays, unions, etc. see [zod documentation](https://zod.dev) for more details.
40+
This is a schema for `Order` object using Zod.
41+
You can create complex schemas by using nested objects, arrays, unions, and other types, see [Zod documentation](https://zod.dev) for more details.
4042

4143
## Parse events
4244

43-
You can parse inbound events using `parser` decorator or middy middleware.
45+
You can parse inbound events using `parser` decorator or middy middleware, or [manually](#manual-parsing) using built-in envelopes and schemas.
4446
Both are also able to parse either an object or JSON string as an input.
4547

46-
???+ note
47-
The decorator and middleware will replace the event object with the parsed schema if successful. This means you might be careful when nesting other decorators that expect event to have a specific structure.
48+
???+ warning
49+
The decorator and middleware will replace the event object with the parsed schema if successful.
50+
Be careful when using multiple decorators that expect event to have a specific structure, the order of evaluation for decorators is from bottom to top.
4851

4952
=== "Middy middleware"
50-
```typescript hl_lines="31"
53+
```typescript hl_lines="32"
5154
--8<-- "docs/snippets/parser/middy.ts"
5255
```
5356

@@ -120,6 +123,11 @@ This can become difficult quite quickly. Parser simplifies the development throu
120123
Envelopes can be used via envelope parameter available in middy and decorator.
121124
Here's an example of parsing a custom schema in an event coming from EventBridge, where all you want is what's inside the detail key.
122125

126+
=== "Middy middleware"
127+
```typescript hl_lines="5 33"
128+
--8<-- "docs/snippets/parser/envelopeMiddy.ts"
129+
```
130+
123131
=== "Decorator"
124132
```typescript hl_lines="5 23"
125133
--8<-- "docs/snippets/parser/envelopeDecorator.ts"
@@ -128,10 +136,7 @@ Here's an example of parsing a custom schema in an event coming from EventBridge
128136
1. Pass `eventBridgeEnvelope` to `parser` decorator
129137
2. `event` is parsed and replaced as `Order` object
130138

131-
=== "Middy middleware"
132-
```typescript hl_lines="5 32"
133-
--8<-- "docs/snippets/parser/envelopeMiddy.ts"
134-
```
139+
135140

136141
The envelopes are functions that take an event and the schema to parse, and return the result of the inner schema.
137142
Depending on the envelope it can be something simple like extracting a key.
@@ -163,30 +168,69 @@ Parser comes with the following built-in envelopes:
163168
| **vpcLatticeV2Envelope** | 1. Parses data using `VpcLatticeSchema`. <br/> 2. Parses `value` key using your schema and returns it. |
164169

165170

171+
## Safe parsing
172+
173+
If you want to parse the event without throwing an error, use the `safeParse` option.
174+
The handler `event` object will be replaced with `ParsedResult<Input?, Oputput?>`, for example `ParsedResult<SqsEvent, Order>`, where `SqsEvent` is the original event and `Order` is the parsed schema.
175+
176+
The `ParsedResult` object will have `success`, `data`, or `error` and `originalEvent` fields, depending on the outcome.
177+
If the parsing is successful, the `data` field will contain the parsed event, otherwise you can access the `error` field and the `originalEvent` to handle the error and recover the original event.
178+
179+
=== "Middy middleware"
180+
```typescript hl_lines="29 32 35 36 41"
181+
--8<-- "docs/snippets/parser/safeParseMiddy.ts"
182+
```
183+
184+
1. Use `safeParse` option to parse the event without throwing an error
185+
2. Check if the result is successful or not and handle the error accordingly
186+
3. Use `data` to access the parsed event
187+
4. Use `error` to handle the error message
188+
5. Use `originalEvent` to get the original event and recover
189+
190+
=== "Decorator"
191+
```typescript hl_lines="26 31 34 37 38"
192+
--8<-- "docs/snippets/parser/safeParseDecorator.ts"
193+
```
194+
195+
1. Use `safeParse` option to parse the event without throwing an error
196+
2. Check if the result is successful or not and handle the error accordingly
197+
3. Use `data` to access the parsed event
198+
4. Use `error` to handle the error message
199+
5. Use `originalEvent` to get the original event and recover
200+
201+
166202
## Manual parsing
167203

168-
You can use built-in envelopes and schemas to parse the incoming events manually:
204+
You can use built-in envelopes and schemas to parse the incoming events manually, without using middy or decorator.
205+
169206

170-
=== "Manual parsing"
207+
=== "Manual parse"
171208
```typescript hl_lines="25 28"
172209
--8<-- "docs/snippets/parser/manual.ts"
173210
```
174-
211+
175212
1. Use `EventBridgeSchema` to parse the event, the `details` fields will be parsed as a generic record.
176213
2. Use `eventBridgeEnvelope` with a combination of `orderSchema` to get `Order` object from the `details` field.
177214

215+
=== "Manual safeParse"
216+
```typescript hl_lines="24 29"
217+
--8<-- "docs/snippets/parser/manualSafeParse.ts"
218+
```
219+
220+
1. Use `safeParse` option to parse the event without throwing an error
221+
2. `safeParse` is also available for envelopes
178222

179223
## Custom validation
180224

181-
Because Parser uses zod, you can use all the features of zod to validate your data.
225+
Because Parser uses Zod, you can use all the features of Zod to validate your data.
182226
For example, you can use `refine` to validate a field or a combination of fields:
183227

184228
=== "Custom validation"
185229
```typescript hl_lines="13 18"
186230
--8<-- "docs/snippets/parser/refine.ts"
187231
```
188232

189-
Zod provides a lot of other features and customization, see [zod documentation](https://zod.dev) for more details.
233+
Zod provides a lot of other features and customization, see [Zod documentation](https://zod.dev) for more details.
190234

191235

192236
## Types
@@ -213,4 +257,4 @@ We are working on a sustainable solution to make them compatible and avoid any b
213257
We recommend to use the types provided by the parser utility.
214258

215259
## Error handling
216-
We don't have any error handling in the utility and propagate all errors from zod, which are thrown as `ZodError`.
260+
We don't have any error handling in the utility and propagate all errors from Zod, which are thrown as `ZodError`.

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,5 @@ extra:
120120
- icon: fontawesome/brands/discord
121121
link: https://discord.gg/B8zZKbbyET
122122
name: Join our Discord Server!
123+
status:
124+
new: New Utility

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)