Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(parser): add docs for parser utility #1835

Merged
merged 24 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions docs/index.md
dreamorosi marked this conversation as resolved.
Show resolved Hide resolved
dreamorosi marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Powertools for AWS Lambda (TypeScript) is a developer toolkit to implement Serve
You can use Powertools for AWS Lambda (TypeScript) in both TypeScript and JavaScript code bases.

???+ tip
Powertools for AWS Lambda is also available for [Python](https://docs.powertools.aws.dev/lambda/python/){target="_blank"}, [Java](https://docs.powertools.aws.dev/lambda/java/){target="_blank"}, and [.NET](https://docs.powertools.aws.dev/lambda/dotnet/){target="_blank"}
Powertools for AWS Lambda is also available for [Python](https://docs.powertools.aws.dev/lambda/python/){target="_blank"}, [Java](https://docs.powertools.aws.dev/lambda/java/){target="_blank"}, and [.NET](https://docs.powertools.aws.dev/lambda/dotnet/){target="_blank"}

??? hint "Support this project by becoming a reference customer, sharing your work, or using Layers :heart:"

Expand Down 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
dreamorosi marked this conversation as resolved.
Show resolved Hide resolved

| 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';
dreamorosi marked this conversation as resolved.
Show resolved Hide resolved
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 })
dreamorosi marked this conversation as resolved.
Show resolved Hide resolved
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';
am29d marked this conversation as resolved.
Show resolved Hide resolved
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