Skip to content

Commit 806b884

Browse files
authored
feat(parser): add helper function to handle JSON stringified fields (#2901)
1 parent b064408 commit 806b884

File tree

9 files changed

+353
-5
lines changed

9 files changed

+353
-5
lines changed

Diff for: docs/core/event-handler/api-gateway.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ This is the sample infrastructure for API Gateway and Lambda Function URLs we ar
4040
=== "API Gateway SAM Template"
4141

4242
```yaml title="AWS Serverless Application Model (SAM) example"
43-
--8<-- "examples/snippets/event-handler/rest/templates/template.yaml"
43+
[//]: # ( --8<-- "examples/snippets/event-handler/rest/templates/template.yaml")
4444
```
4545

4646
=== "Lambda Function URL SAM Template"
4747

4848
```yaml title="AWS Serverless Application Model (SAM) example"
49-
--8<-- "examples/event_handler_lambda_function_url/sam/template.yaml"
49+
[//]: # ( --8<-- "examples/event_handler_lambda_function_url/sam/template.yaml")
5050
```
5151

5252
<!-- remove line below while editing this doc & put it back until the doc has reached its first draft -->

Diff for: docs/utilities/parser.md

+28
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,34 @@ You can extend every built-in schema to include your own schema, and yet have al
109109
--8<-- "examples/snippets/parser/examplePayload.json"
110110
```
111111

112+
If you want to extend a schema and transform a JSON stringified payload to an object, you can use helper function `JSONStringified`:
113+
114+
=== "AlbSchema with JSONStringified"
115+
```typescript hl_lines="12"
116+
--8<-- "examples/snippets/parser/extendAlbSchema.ts"
117+
```
118+
119+
1. Extend built-in `AlbSchema` using JSONStringified function to transform your payload
120+
121+
=== "Alb exmaple payload"
122+
123+
```json hl_lines="26"
124+
--8<-- "examples/snippets/parser/exampleAlbPayload.json"
125+
```
126+
127+
=== "SQS Schema with JSONStringified"
128+
```typescript hl_lines="23-25 30 34"
129+
--8<-- "examples/snippets/parser/extendSqsSchema.ts"
130+
```
131+
132+
1. make sure to set your schema to the correct key in the JSON payload
133+
134+
=== "SQS exmaple payload"
135+
136+
```json hl_lines="6 28"
137+
--8<-- "examples/snippets/parser/exampleSqsPayload.json"
138+
```
139+
112140
## Envelopes
113141

114142
When trying to parse your payload you might encounter the following situations:

Diff for: examples/snippets/parser/exampleAlbPayload.json

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"requestContext": {
3+
"elb": {
4+
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"
5+
}
6+
},
7+
"httpMethod": "GET",
8+
"path": "/lambda",
9+
"queryStringParameters": {
10+
"query": "1234ABCD"
11+
},
12+
"headers": {
13+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
14+
"accept-encoding": "gzip",
15+
"accept-language": "en-US,en;q=0.9",
16+
"connection": "keep-alive",
17+
"host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com",
18+
"upgrade-insecure-requests": "1",
19+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
20+
"x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476",
21+
"x-forwarded-for": "72.12.164.125",
22+
"x-forwarded-port": "80",
23+
"x-forwarded-proto": "http",
24+
"x-imforwards": "20"
25+
},
26+
"body": "{\"name\":\"Walter\", \"age\": 50}",
27+
"isBase64Encoded": false
28+
}

Diff for: examples/snippets/parser/exampleSqsPayload.json

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"Records": [
3+
{
4+
"messageId": "059f36b4-87a3-44ab-83d2-661975830a7d",
5+
"receiptHandle": "AQEBwJnKyrHigUMZj6rYigCgxlaS3SLy0a...",
6+
"body": "{\"name\": \"John Doe\", \"age\": 30}",
7+
"attributes": {
8+
"ApproximateReceiveCount": "1",
9+
"SentTimestamp": "1545082649183",
10+
"SenderId": "AIDAIENQZJOLO23YVJ4VO",
11+
"ApproximateFirstReceiveTimestamp": "1545082649185"
12+
},
13+
"messageAttributes": {
14+
"testAttr": {
15+
"stringValue": "100",
16+
"binaryValue": "base64Str",
17+
"dataType": "Number"
18+
}
19+
},
20+
"md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3",
21+
"eventSource": "aws:sqs",
22+
"eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",
23+
"awsRegion": "us-east-2"
24+
},
25+
{
26+
"messageId": "2e1424d4-f796-459a-8184-9c92662be6da",
27+
"receiptHandle": "AQEBzWwaftRI0KuVm4tP+/7q1rGgNqicHq...",
28+
"body": "{\"name\": \"foo\", \"age\": 10}",
29+
"attributes": {
30+
"ApproximateReceiveCount": "1",
31+
"SentTimestamp": "1545082650636",
32+
"SenderId": "AIDAIENQZJOLO23YVJ4VO",
33+
"ApproximateFirstReceiveTimestamp": "1545082650649",
34+
"DeadLetterQueueSourceArn": "arn:aws:sqs:us-east-2:123456789012:my-queue-dead"
35+
},
36+
"messageAttributes": {},
37+
"md5OfBody": "e4e68fb7bd0e697a0ae8f1bb342846b3",
38+
"eventSource": "aws:sqs",
39+
"eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:my-queue",
40+
"awsRegion": "us-east-2"
41+
}
42+
]
43+
}

Diff for: examples/snippets/parser/extendAlbSchema.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { JSONStringified } from '@aws-lambda-powertools/parser/helpers';
2+
import { AlbSchema } from '@aws-lambda-powertools/parser/schemas';
3+
import { z } from 'zod';
4+
5+
const customSchema = z.object({
6+
name: z.string(),
7+
age: z.number(),
8+
});
9+
10+
const extendedSchema = AlbSchema.extend({
11+
body: JSONStringified(customSchema), // (1)!
12+
});

Diff for: examples/snippets/parser/extendSqsSchema.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { JSONStringified } from '@aws-lambda-powertools/parser/helpers';
2+
import {
3+
SqsRecordSchema,
4+
SqsSchema,
5+
} from '@aws-lambda-powertools/parser/schemas';
6+
import { z } from 'zod';
7+
8+
const customSchema = z.object({
9+
name: z.string(),
10+
age: z.number(),
11+
});
12+
13+
const extendedSchema = SqsSchema.extend({
14+
Records: z.array(
15+
SqsRecordSchema.extend({
16+
body: JSONStringified(customSchema), // (1)!
17+
})
18+
),
19+
});

Diff for: packages/parser/package.json

+19-3
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,21 @@
167167
"require": "./lib/cjs/envelopes/vpc-latticev2.js",
168168
"import": "./lib/esm/envelopes/vpc-latticev2.js"
169169
},
170+
"./helpers": {
171+
"require": "./lib/cjs/helpers.js",
172+
"import": "./lib/esm/helpers.js"
173+
},
170174
"./types": {
171175
"require": "./lib/cjs/types/index.js",
172176
"import": "./lib/esm/types/index.js"
173177
}
174178
},
175179
"typesVersions": {
176180
"*": {
177-
"types": ["./lib/cjs/types/index.d.ts", "./lib/esm/types/index.d.ts"],
181+
"types": [
182+
"./lib/cjs/types/index.d.ts",
183+
"./lib/esm/types/index.d.ts"
184+
],
178185
"middleware": [
179186
"./lib/cjs/middleware/parser.d.ts",
180187
"./lib/esm/middleware/parser.d.ts"
@@ -227,7 +234,10 @@
227234
"./lib/cjs/schemas/lambda.d.ts",
228235
"./lib/esm/schemas/lambda.d.ts"
229236
],
230-
"schemas/s3": ["./lib/cjs/schemas/s3.d.ts", "./lib/esm/schemas/s3.d.ts"],
237+
"schemas/s3": [
238+
"./lib/cjs/schemas/s3.d.ts",
239+
"./lib/esm/schemas/s3.d.ts"
240+
],
231241
"schemas/ses": [
232242
"./lib/cjs/schemas/ses.d.ts",
233243
"./lib/esm/schemas/ses.d.ts"
@@ -303,12 +313,18 @@
303313
"envelopes/vpc-latticev2": [
304314
"./lib/cjs/envelopes/vpc-latticev2.d.ts",
305315
"./lib/esm/envelopes/vpc-latticev2.d.ts"
316+
],
317+
"helpers": [
318+
"./lib/cjs/helpers.d.ts",
319+
"./lib/esm/helpers.d.ts"
306320
]
307321
}
308322
},
309323
"main": "./lib/cjs/index.js",
310324
"types": "./lib/cjs/index.d.ts",
311-
"files": ["lib"],
325+
"files": [
326+
"lib"
327+
],
312328
"repository": {
313329
"type": "git",
314330
"url": "git+https://github.com/aws-powertools/powertools-lambda-typescript.git"

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

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { type ZodSchema, z } from 'zod';
2+
3+
/**
4+
* A helper function to parse a JSON string and validate it against a schema.
5+
* Use it for built-in schemas like `AlbSchema`, `ApiGatewaySchema`, etc. to extend them with your customer schema.
6+
*
7+
* @example
8+
* ```typescript
9+
* import { JSONStringified } from '@aws-lambda-powertools/parser/helpers';
10+
* import { AlbSchema } from '@aws-lambda-powertools/parser/schemas';
11+
* import { z } from 'zod';
12+
*
13+
* const customSchema = z.object({
14+
* name: z.string(),
15+
* age: z.number(),
16+
* });
17+
*
18+
* const extendedSchema = AlbSchema.extend({
19+
* body: JSONStringified(customSchema),
20+
* });
21+
*
22+
* ```
23+
*
24+
* @param schema - The schema to validate the JSON string against
25+
*/
26+
const JSONStringified = (schema: ZodSchema) =>
27+
z
28+
.string()
29+
.transform((str, ctx) => {
30+
try {
31+
return JSON.parse(str);
32+
} catch (err) {
33+
ctx.addIssue({
34+
code: 'custom',
35+
message: 'Invalid JSON',
36+
});
37+
}
38+
})
39+
.pipe(schema);
40+
41+
export { JSONStringified };

0 commit comments

Comments
 (0)