使用适用于 Amazon Lambda 的 Powertools (TypeScript) 验证事件负载

作者: Alexander Schüren |

在这篇文章中,了解新的适用于 Amazon Lambda 的 Powertools(TypeScript)解析器实用程序如何帮助您轻松验证有效负载并使您的 Lambda 函数更具弹性。

验证输入有效载荷是构建安全可靠应用程序的重要方面。这样可以确保应用程序接收到的数据可以正常处理意外或恶意输入,并防止有害的下游处理。在编写 Amazon Lambda 函数时,开发者需要验证和验证负载,并确保特定字段和值正确且可以安全处理。

适用于 Amazon Lambda 的 Powertools 是一款开发人员工具包,支持 Python、NodeJS/TypeScript、Java 和 .NET。它有助于实施无服务器优秀实践并提高开发人员速度。适用于 Amazon Lambda 的 Powertools(TypeScript)正在推出一个新的解析器工具,以帮助开发者更轻松地在他们的 Lambda 函数中实现验证。

为什么有效载荷验证很重要

验证有效负载可以使您的 Lambda 函数更具弹性。将技术和业务信息结合在一起的有效载荷也可能难以验证。这需要在 Lambda 函数代码中编写验证逻辑。这可能包括用于检查负载值的几个 if 语句到基于自定义业务逻辑的一系列复杂的验证步骤。您可能需要将有效负载的技术信息(如亚马逊云科技区域、AccountID)、事件源和事件内部业务信息(例如 ProductID 和付款详情)的验证分开。

了解事件对象的结构和值以及如何提取相关信息可能很困难。例如,Amazon SQS 事件的正文字段具有字符串值,可以是 JSON 文档。Amazon EventBridge 在详细信息字段中有一个对象,您无需进一步转换即可直接读取该对象。您可能需要解压缩、解码、转换和验证特定字段内的有效负载。了解许多转换层可能很复杂,尤其是当您的事件对象是多个服务调用的结果时。

使用适用于 Amazon Lambda 的 Powertools (TypeScript) 解析器实用工具

适用于 Amazon Lambda (TypeScript) 的 Powertools 是一个模块化库。您可以有选择地安装诸如记录器、跟踪器、指标、批处理、指数等功能。您可以在 TypeScript 和 JavaScript 代码库中使用适用于 Amazon Lambda 的 Powertools。新的解析器实用程序简化了验证,并使用了流行的验证库 Zod。

您可以使用 middyjs 中间件将解析器用作方法装饰器,也可以在所有 Lambda 提供的 NodeJS 运行时中手动使用

要使用该实用程序,请使用 NPM 或您选择的任何包管理器安装 Powertools 解析器实用程序和 Zod (<v3.x):

npm install @aws-lambda-powertools/parser zod@~3

您可以使用 Zod 定义架构。以下是用于验证事件的简单顺序架构的示例:

import { z } from 'zod';
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(),
			})
		),
	});
export { orderSchema };

此订单架构定义了 ID、描述和项目列表。您可以从简单数字指定值类型,将其缩小到正数或文字,或者更复杂的值,例如弹药、数组甚至其他架构。Zod 提供了大量可供您使用的值类型。

将解析器装饰器添加到您的处理函数中,设置架构参数,并使用此架构解析事件对象。

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(),
		})
	),
});

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});
			// process order item from the event
		}
	}
}
	
const myFunction = new Lambda();

export const handler = myFunction.handler.bind(myFunction);

请注意,这 z.infer 有助于从架构中提取 Order 类型,从而改善使用 TypeScript 时使用自动完成功能的开发体验。Zod 解析整个对象,包括嵌套字段,并合并报告所有错误,而不是仅返回第一个错误。

为亚马逊云科技服务使用内置架构

更常见的场景是验证来自触发 Lambda 函数的亚马逊云科技服务事件,包括 Amazon SQS、Amazon EventBridge 等。为了简化此操作,Powertools 包括了可用于亚马逊云科技事件的预建架构。

要解析传入的 Amazon EventBridge 事件,请在解析器配置中设置内置架构:

import {LambdaInterface} from '@aws-lambda-powertools/commons/types';
import {Context} from 'aws-lambda';
import {parser} from '@aws-lambda-powertools/parser';
import {EventBridgeSchema} from '@aws-lambda-powertools/parser/schemas';
import type {EventBridgeEvent} from '@aws-lambda-powertools/parser/types';

class Lambda implements LambdaInterface {  
    @parser({schema: EventBridgeSchema})  
	public async handler(event: EventBridgeEvent, _context: Context): Promise<void> {    
	    // event is parsed but the detail field is not specified  
	}
}

const myFunction = new Lambda();

export const handler = myFunction.handler.bind(myFunction);

事件对象将在运行时进行解析和验证,TypeScript 类型的 EventBridgeEvent 可帮助你了解结构并在开发期间访问字段。在此示例中,您仅解析 EventBridge 事件对象,因此详细信息字段可以是任意对象。

您还可以扩展内置的 EventBridge 架构,并使用自定义 OderSchema 覆盖详细信息字段。

import {LambdaInterface} from '@aws-lambda-powertools/commons/types';
import {Context} from 'aws-lambda';
import {parser} from '@aws-lambda-powertools/parser';
import {EventBridgeSchema} from '@aws-lambda-powertools/parser/schemas';
import {z} from 'zod';

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(),
		}), 
	),
});

const eventBridgeOrderSchema = EventBridgeSchema.extend({  detail: orderSchema,});

type EventBridgeOrder = z.infer<typeof eventBridgeOrderSchema>;

class Lambda implements LambdaInterface {  
    @parser({schema: eventBridgeOrderSchema})  public async handler(event: EventBridgeOrder, _context: Context): Promise<void> {
	    // event.detail is now parsed as orderSchema  
	}
}

const myFunction = new Lambda();

export const handler = myFunction.handler.bind(myFunction);

解析器会验证整个 EventBridge 事件的完整结构,包括自定义业务对象。使用 .extend 或其他 Zod 架构函数更改内置架构的任何字段并自定义有效负载验证。

使用带有自定义架构的信封

在某些情况下,您只需要负载的自定义部分,例如,EventBridge 事件的详细字段或 SQS 记录的正文。这需要您手动解析事件架构,提取必填字段,然后使用自定义架构再次对其进行解析。这很复杂,因为你必须知道确切的有效载荷字段以及如何对其进行转换和解析。

Powertools 解析器实用程序有助于使用 Envelopes 解决这个问题。信封是具有内置逻辑的架构对象,用于提取自定义有效负载。

以下是 EventBridgeEnvelope 工作原理的示例:

import {LambdaInterface} from '@aws-lambda-powertools/commons/types';
import {Context} from 'aws-lambda';
import {parser} from '@aws-lambda-powertools/parser';
import {EventBridgeEnvelope} from '@aws-lambda-powertools/parser/envelopes';
import {z} from 'zod';

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(),
		}), 
	),
});

type Order = z.infer<typeof orderSchema>;

class Lambda implements LambdaInterface {  
    @parser({schema: orderSchema, envelope: EventBridgeEnvelope})  public async handler(event: Order, _context: Context): Promise<void> {
        // event is now typed as Order inferred from the orderSchema  
    }
}

const myFunction = new Lambda();

export const handler = myFunction.handler.bind(myFunction);

通过设置架构和信封,解析器实用程序知道如何组合两个参数、提取和验证事件中的自定义负载。Powertools 解析器根据架构定义转换事件对象,这样您就可以专注于处理函数内部代码的关键业务部分。

安全解析

如果对象与提供的 Zod 架构不匹配,则默认情况下,解析器会抛出 parserError。如果您需要控制验证错误并需要实现自定义错误处理,请使用 SafeParse 选项。

以下是如何将失败的验证作为处理函数中的指标捕获的示例:

import {Logger} from "@aws-lambda-powertools/logger";
import {LambdaInterface} from "@aws-lambda-powertools/commons/types";
import {parser} from "@aws-lambda-powertools/parser";
import {orderSchema} from "../app/schema";import {z} from "zod";
import {EventBridgeEnvelope} from "@aws-lambda-powertools/parser/envelopes";
import {Metrics, MetricUnit} from "@aws-lambda-powertools/metrics";
import {ParsedResult, EventBridgeEvent} from "@aws-lambda-powertools/parser/types";

const logger = new Logger();

const metrics = new Metrics();

type Order = z.infer<typeof orderSchema>;

class Lambda implements LambdaInterface {  

    @metrics.logMetrics() 
	@parser({schema: orderSchema, envelope: EventBridgeEnvelope, safeParse: true})  
	public async handler(event: ParsedResult<EventBridgeEvent, Order>, _context: unknown): Promise<void> {
	    if (!event.success) {      
		    // failed validation      
			metrics.addMetric('InvalidPayload', MetricUnit.Count, 1);      
			logger.error('Invalid payload', event.originalEvent);    
		} else {      
		    // successful validation      
			for (const item of event.data.items) {        
			    logger.info('Processing item', item);        
				// event.data is typed as Order      
			}   
		}  
	}
}

const myFunction = new Lambda();

export const handler = myFunction.handler.bind(myFunction);

safeParse 选项设置为 true 不会引发错误,但会返回修改后的事件对象,该对象具有成功标志以及错误或数据字段,具体取决于验证结果。然后,您可以创建自定义错误处理,例如增加 InvalidPayload 指标并访问 OriginalEvent 来记录错误。

要成功进行验证,您可以访问数据字段并处理负载。请注意,事件对象类型现在是 parsedResult,其中 EventBridgeEvent 作为输入,Order 作为输出类型。

自定义验证

有时,您可能需要更复杂的业务规则来进行验证。由于 Parser 内置架构是 Zod 对象,因此您可以通过应用 .extend.refine.transform 和其他 Zod 运算符来自定义验证。以下是 OrderSchema 的复杂规则示例:

import {z} from 'zod';

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(),  
	})).refine((items) => items.length > 0, {
	    message: 'Order must have at least one item',  
	}),
})  
    .refine((order) => order.id > 100 && order.items.length > 100, {
	    message:      'All orders with more than 100 items must have an id greater than 100', 
	});

使用 .refine 商品字段检查订单中是否至少有一件商品。您还可以在此处 order.id 和处合并多个字段 order.items.length,为超过 100 件商品的订单制定特定规则。请记住,它在验证步骤中 .refine 运行,.transform 将在验证之后应用。这允许您更改数据的形状以标准化输出。

结论

适用于 Amazon Lambda 的 Powertools(TypeScript)正在推出一个新的解析器工具,它可以更轻松地为你的 Lambda 函数添加验证。通过依赖广受欢迎的验证库 Zod,Powertools 为包括 Amazon SQS、Amazon DynamoDB、Amazon EventBridge 在内的流行的亚马逊云科技服务集成提供了大量的内置架构。开发人员可以使用这些架构来验证其事件有效负载,也可以根据其业务需求对其进行自定义。

访问文档以了解更多信息,并加入我们的 Powertools 社区 Discord,与志同道合的无服务器爱好者交流。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。