Powertools for AWS Lambda (Python) の「Validation」を使うと AWS Lambda 関数に渡されたイベント情報のバリデーションを JSON Schema に沿って実現できる.例えば,必須パラメータ・文字数制限・ENUM・正規表現などをチェックできる👌
Powertools for AWS Lambda (Python) 自体は Tracer / Logger / Event Source Data Classes などをよく使うけど,Validation は今まで活用できてなく,試してみたらとても便利だったので,今回試した結果を簡単にまとめておく \( 'ω')/
docs.powertools.aws.dev
検証環境
今回は AWS SAM を使って Amazon API Gateway (REST API) と AWS Lambda 関数を構築する.あくまでサンプルとして Amazon API Gateway の /
に POST リクエストを送るとバリデーションロジックを含んだ AWS Lambda 関数が実行されるようにした.また Powertools は Lambda Layer でセットアップする.
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
FunctionName: powertools-validation
CodeUri: src/
Handler: app.lambda_handler
Runtime: python3.12
Architectures:
- x86_64
Layers:
- arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:67
Events:
Api:
Type: Api
Properties:
Path: /
Method: POST
最終的なディレクトリ構成は以下のようになる💡
├── events
│ └── event.json
├── samconfig.toml
├── src
│ └── app.py
└── template.yaml
👾 app.py
今回はバリデーションを紹介するサンプルとして,特にロジックはなく Amazon API Gateway への POST リクエストに対して 200 OK
もしくは 400 BAD_REQUEST
を返す実装にした.また AWS Lambda 関数のベストプラクティスを参考に Handler とロジックを分割して main()
にまとめてある(実際にはもっと細かく分割しても良さそう).
そして,今回は超簡易的な「TODO アプリ」を例として,title / category / description / link
というパラメータを受け取る API のバリデーションを実装した.パラメータごとのバリデーション要件は以下のコードの SCHEMA
を見てもらえればと❗️さらに Powertools for AWS Lambda (Python) の Validation ではバリデーションロジックを @validator
デコレータを使った実装と validate()
関数を使った実装から選べる.どちらも試してみて,個人的には以下の2つの理由から validate()
関数を使うのが良いと思った💡
validate()
関数は Lambda コンテキストに依存してなくてローカル開発がしやすかった
- 例外発生時のハンドリングなどを柔軟に実装しやすかった
また validate()
関数を呼び出すときに envelope
を指定できて,イベントオブジェクトの中からバリデーションする箇所を限定できる.今回は Amazon API Gateway (REST API) から渡されるイベントをバリデーションするため,envelopes.API_GATEWAY_REST
を指定した.すると自動的に body
がバリデーション対象になる👌現状は8種類の envelope
が提供されている \( 'ω')/
- API_GATEWAY_HTTP
- API_GATEWAY_REST
- CLOUDWATCH_EVENTS_SCHEDULED
- CLOUDWATCH_LOGS
- EVENTBRIDGE
- KINESIS_DATA_STREAM
- SNS
- SQS
import json
from aws_lambda_powertools.utilities.validation import SchemaValidationError, envelopes, validate
from http import HTTPStatus
from jmespath.exceptions import JMESPathTypeError
SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema',
'type': 'object',
'required': ['title', 'category'],
'properties': {
'title': {
'type': 'string',
'pattern': '^[a-zA-Z0-9_]*$'
},
'category': {
'type': 'string',
'enum': ['Python', 'Go', 'Java']
},
'description': {
'type': 'string',
'minLength': 10,
'maxLength': 100
},
'link': {
'type': 'string',
'format': 'uri'
}
},
}
def main(event):
try:
validate(event=event, schema=SCHEMA, envelope=envelopes.API_GATEWAY_REST)
except JMESPathTypeError as e:
return {
'statusCode': HTTPStatus.BAD_REQUEST,
'body': json.dumps({'message': str(e)})
}
except json.JSONDecodeError as e:
return {
'statusCode': HTTPStatus.BAD_REQUEST,
'body': json.dumps({'message': e.msg})
}
except SchemaValidationError as e:
return {
'statusCode': HTTPStatus.BAD_REQUEST,
'body': json.dumps({'message': e.validation_message})
}
return {
'statusCode': HTTPStatus.OK,
'body': json.dumps({'message': 'ok'})
}
def lambda_handler(event, context):
return main(event)
if __name__ == '__main__':
with open('../events/event.json', 'r') as f:
event = json.load(f)
main(event)
動作確認
data must contain ['category', 'title'] properties
event.json
{}
必須の title
と category
プロパティがなくバリデーションエラー🙅♂️
$ curl -s -X POST --data @event.json ${ENDPOINT}
{"message": "data must contain ['category', 'title'] properties"}
data.title must match pattern ^[a-zA-Z0-9_]*$
event.json
{
"title": "Powertoolsを試す",
"category": "Python"
}
title
に日本語を含んでいるためバリデーションエラー🙅♂️
$ curl -s -X POST --data @event.json ${ENDPOINT}
{"message": "data.title must match pattern ^[a-zA-Z0-9_]*$"}
data.category must be one of ['Python', 'Go', 'Java']
event.json
{
"title": "Powertools",
"category": "AWS"
}
category
の ENUM 以外の値を指定しているためバリデーションエラー🙅♂️
$ curl -s -X POST --data @event.json ${ENDPOINT}
{"message": "data.category must be one of ['Python', 'Go', 'Java']"}
data.description must be longer than or equal to 10 characters
event.json
{
"title": "Powertools",
"category": "Python",
"description": ""
}
description
が10文字以上になっていないためバリデーションエラー🙅♂️
$ curl -s -X POST --data @event.json ${ENDPOINT}
{"message": "data.description must be longer than or equal to 10 characters"}
data.link must be uri
event.json
{
"title": "Powertools",
"category": "Python",
"description": "Try Powertools for AWS Lambda.",
"link": "docs.powertools.aws.dev"
}
link
が URL 形式になっていないためバリデーションエラー🙅♂️
$ curl -s -X POST --data @event.json ${ENDPOINT}
{"message": "data.link must be uri"}
まとめ
Powertools for AWS Lambda (Python) の「Validation」を使うと AWS Lambda 関数に渡されたイベント情報のバリデーションを柔軟に実装できてとても便利だった❗️
今後は積極的に使っていくぞー \( 'ω')/
関連リンク🔗
github.com