Skip to content

Commit

Permalink
docs(parser): add docs for parser utility (#1835)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
am29d and dreamorosi authored Apr 5, 2024
1 parent badfef5 commit e0de20b
Show file tree
Hide file tree
Showing 20 changed files with 718 additions and 10 deletions.
11 changes: 6 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ You can include Powertools for AWS Lambda (TypeScript) Lambda Layer using [AWS L
| `il-central-1` | [arn:aws:lambda:il-central-1:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:3](#){: .copyMe}:clipboard: |

??? note "Click to expand and copy code snippets for popular frameworks"

=== "SAM"

```yaml hl_lines="5"
Expand Down Expand Up @@ -252,7 +252,7 @@ You can include Powertools for AWS Lambda (TypeScript) Lambda Layer using [AWS L
!!! 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"

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

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

## Instrumentation

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

* **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.
* **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.
Expand All @@ -289,11 +289,12 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al
| [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 |
| [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. |
| [Batch Processing](./utilities/batch.md) | Utility to handle partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. |
| [Parser](./utilities/parser.md) | Utility to parse and validate AWS Lambda event payloads using Zod, a TypeScript-first schema declaration and validation library. |

## Environment variables

???+ info
Explicit parameters take precedence over environment variables
Explicit parameters take precedence over environment variables

| Environment variable | Description | Utility | Default |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ------------------- |
Expand Down Expand Up @@ -351,4 +352,4 @@ These are our core principles to guide our decision making.
* **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.
* **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)
* **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.


5 changes: 4 additions & 1 deletion docs/snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "docs",
"version": "2.0.3",
"description": "A collection code snippets for the Powertools for AWS Lambda (TypeScript) docs",
"type": "module",
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com"
Expand Down Expand Up @@ -34,10 +35,12 @@
"@aws-sdk/client-secrets-manager": "^3.543.0",
"@aws-sdk/client-ssm": "^3.540.0",
"@aws-sdk/util-dynamodb": "^3.540.0",
"@middy/core": "^4.7.0",
"aws-sdk": "^2.1589.0",
"aws-sdk-client-mock": "^4.0.0",
"aws-sdk-client-mock-jest": "^4.0.0",
"axios": "^1.6.8",
"hashi-vault-js": "^0.4.14"
"hashi-vault-js": "^0.4.14",
"zod": "^3.22.4"
}
}
35 changes: 35 additions & 0 deletions docs/snippets/parser/decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Context } from 'aws-lambda';
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { parser } from '@aws-lambda-powertools/parser';
import { z } from 'zod';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});

type Order = z.infer<typeof orderSchema>;

class Lambda implements LambdaInterface {
@parser({ schema: orderSchema })
public async handler(event: Order, _context: Context): Promise<void> {
// event is now typed as Order
for (const item of event.items) {
logger.info('Processing item', { item });
}
}
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
36 changes: 36 additions & 0 deletions docs/snippets/parser/envelopeDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Context } from 'aws-lambda';
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { parser } from '@aws-lambda-powertools/parser';
import { z } from 'zod';
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});

type Order = z.infer<typeof orderSchema>;

class Lambda implements LambdaInterface {
@parser({ schema: orderSchema, envelope: EventBridgeEnvelope }) // (1)!
public async handler(event: Order, _context: Context): Promise<void> {
// event is now typed as Order
for (const item of event.items) {
logger.info('Processing item', item); // (2)!
}
}
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
37 changes: 37 additions & 0 deletions docs/snippets/parser/envelopeMiddy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Context } from 'aws-lambda';
import { parser } from '@aws-lambda-powertools/parser/middleware';
import { z } from 'zod';
import middy from '@middy/core';
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});

type Order = z.infer<typeof orderSchema>;

const lambdaHandler = async (
event: Order,
_context: Context
): Promise<void> => {
for (const item of event.items) {
// item is parsed as OrderItem
logger.info('Processing item', { item });
}
};

export const handler = middy(lambdaHandler).use(
parser({ schema: orderSchema, envelope: EventBridgeEnvelope })
);
21 changes: 21 additions & 0 deletions docs/snippets/parser/examplePayload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "0",
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
"detail-type": "OrderPurchased",
"source": "OrderService",
"account": "111122223333",
"time": "2020-10-22T18:43:48Z",
"region": "us-west-1",
"resources": ["some_additional"],
"detail": {
"id": 10876546789,
"description": "My order",
"items": [
{
"id": 1015938732,
"quantity": 1,
"description": "item xpto"
}
]
}
}
40 changes: 40 additions & 0 deletions docs/snippets/parser/extend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { Context } from 'aws-lambda';
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
import { parser } from '@aws-lambda-powertools/parser';
import { z } from 'zod';
import { EventBridgeSchema } from '@aws-lambda-powertools/parser/schemas';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});

const orderEventSchema = EventBridgeSchema.extend({
detail: orderSchema, // (1)!
});

type OrderEvent = z.infer<typeof orderEventSchema>;

class Lambda implements LambdaInterface {
@parser({ schema: orderEventSchema }) // (2)!
public async handler(event: OrderEvent, _context: Context): Promise<void> {
for (const item of event.detail.items) {
// process OrderItem
logger.info('Processing item', { item }); // (3)!
}
}
}

const myFunction = new Lambda();
export const handler = myFunction.handler.bind(myFunction);
33 changes: 33 additions & 0 deletions docs/snippets/parser/manual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Context } from 'aws-lambda';
import { z } from 'zod';
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import { EventBridgeSchema } from '@aws-lambda-powertools/parser/schemas';
import type { EventBridgeEvent } from '@aws-lambda-powertools/parser/types';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});
type Order = z.infer<typeof orderSchema>;

export const handler = async (
event: EventBridgeEvent,
_context: Context
): Promise<void> => {
const parsedEvent = EventBridgeSchema.parse(event); // (1)!
logger.info('Parsed event', parsedEvent);

const orders: Order = EventBridgeEnvelope.parse(event, orderSchema); // (2)!
logger.info('Parsed orders', orders);
};
35 changes: 35 additions & 0 deletions docs/snippets/parser/manualSafeParse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Context } from 'aws-lambda';
import { z } from 'zod';
import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes';
import { EventBridgeSchema } from '@aws-lambda-powertools/parser/schemas';
import type { EventBridgeEvent } from '@aws-lambda-powertools/parser/types';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});

export const handler = async (
event: EventBridgeEvent,
_context: Context
): Promise<void> => {
const parsedEvent = EventBridgeSchema.safeParse(event); // (1)!
parsedEvent.success
? logger.info('Event parsed successfully', parsedEvent.data)
: logger.error('Event parsing failed', parsedEvent.error);
const parsedEvenlope = EventBridgeEnvelope.safeParse(event, orderSchema); // (2)!
parsedEvenlope.success
? logger.info('Event envelope parsed successfully', parsedEvenlope.data)
: logger.error('Event envelope parsing failed', parsedEvenlope.error);
};
36 changes: 36 additions & 0 deletions docs/snippets/parser/middy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Context } from 'aws-lambda';
import { parser } from '@aws-lambda-powertools/parser/middleware';
import { z } from 'zod';
import middy from '@middy/core';
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger();

const orderSchema = z.object({
id: z.number().positive(),
description: z.string(),
items: z.array(
z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
})
),
optionalField: z.string().optional(),
});

type Order = z.infer<typeof orderSchema>;

const lambdaHandler = async (
event: Order,
_context: Context
): Promise<void> => {
for (const item of event.items) {
// item is parsed as OrderItem
logger.info('Processing item', { item });
}
};

export const handler = middy(lambdaHandler).use(
parser({ schema: orderSchema })
);
21 changes: 21 additions & 0 deletions docs/snippets/parser/refine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from 'zod';

const orderItemSchema = z.object({
id: z.number().positive(),
quantity: z.number(),
description: z.string(),
});

export const orderSchema = z
.object({
id: z.number().positive(),
description: z.string(),
items: z.array(orderItemSchema).refine((items) => items.length > 0, {
message: 'Order must have at least one item', // (1)!
}),
optionalField: z.string().optional(),
})
.refine((order) => order.id > 100 && order.items.length > 100, {
message:
'All orders with more than 100 items must have an id greater than 100', // (2)!
});
Loading

0 comments on commit e0de20b

Please sign in to comment.