From 80d4b8796200d47cdd0fb4ba750a9afb031229b0 Mon Sep 17 00:00:00 2001 From: Vatsal Goel <144617902+VatsalGoel3@users.noreply.github.com> Date: Mon, 31 Mar 2025 01:00:22 -0600 Subject: [PATCH 1/5] feat(parser): add AppSync resolver event model to parser utility --- .../utilities/parser/models/__init__.py | 4 + .../utilities/parser/models/appsync.py | 70 +++++++++++++++++ tests/events/appsync_resolver_event.json | 78 +++++++++++++++++++ tests/unit/parser/_pydantic/test_appsync.py | 43 ++++++++++ 4 files changed, 195 insertions(+) create mode 100644 aws_lambda_powertools/utilities/parser/models/appsync.py create mode 100644 tests/events/appsync_resolver_event.json create mode 100644 tests/unit/parser/_pydantic/test_appsync.py diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index 8d6be2f40e0..917970ab604 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -27,6 +27,10 @@ RequestContextV2AuthorizerJwt, RequestContextV2Http, ) +from .appsync import ( + AppSyncResolverEventModel, + AppSyncBatchResolverEventModel, +) from .bedrock_agent import ( BedrockAgentEventModel, BedrockAgentModel, diff --git a/aws_lambda_powertools/utilities/parser/models/appsync.py b/aws_lambda_powertools/utilities/parser/models/appsync.py new file mode 100644 index 00000000000..fe65d932332 --- /dev/null +++ b/aws_lambda_powertools/utilities/parser/models/appsync.py @@ -0,0 +1,70 @@ +from typing import Optional, List, Dict, Union, Any +from pydantic import BaseModel + +class AppSyncIamIdentity(BaseModel): + accountId: str + cognitoIdentityPoolId: Optional[str] + cognitoIdentityId: Optional[str] + sourceIp: List[str] + username: str + userArn: str + cognitoIdentityAuthType: Optional[str] + cognitoIdentityAuthProvider: Optional[str] + + +class AppSyncCognitoIdentity(BaseModel): + sub: str + issuer: str + username: str + claims: Dict[str, Any] + sourceIp: List[str] + defaultAuthStrategy: str + groups: Optional[List[str]] + + +class AppSyncOidcIdentity(BaseModel): + claims: Dict[str, Any] + issuer: str + sub: str + + +class AppSyncLambdaIdentity(BaseModel): + resolverContext: Dict[str, Any] + + +AppSyncIdentity = Union[ + AppSyncIamIdentity, + AppSyncCognitoIdentity, + AppSyncOidcIdentity, + AppSyncLambdaIdentity, +] + + +class AppSyncRequestModel(BaseModel): + domainName: Optional[str] + headers: Dict[str, str] + + +class AppSyncInfoModel(BaseModel): + selectionSetList: List[str] + selectionSetGraphQL: str + parentTypeName: str + fieldName: str + variables: Dict[str, Any] + + +class AppSyncPrevModel(BaseModel): + result: Dict[str, Any] + + +class AppSyncResolverEventModel(BaseModel): + arguments: Dict[str, Any] + identity: Optional[AppSyncIdentity] + source: Optional[Dict[str, Any]] + request: AppSyncRequestModel + info: AppSyncInfoModel + prev: Optional[AppSyncPrevModel] + stash: Dict[str, Any] + + +AppSyncBatchResolverEventModel = List[AppSyncResolverEventModel] \ No newline at end of file diff --git a/tests/events/appsync_resolver_event.json b/tests/events/appsync_resolver_event.json new file mode 100644 index 00000000000..1b56d4dc93c --- /dev/null +++ b/tests/events/appsync_resolver_event.json @@ -0,0 +1,78 @@ +{ + "typeName": "Merchant", + "fieldName": "locations", + "arguments": { + "page": 2, + "size": 1, + "name": "value" + }, + "identity": { + "claims": { + "sub": "07920713-4526-4642-9c88-2953512de441", + "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID", + "aud": "58rc9bf5kkti90ctmvioppukm9", + "event_id": "7f4c9383-abf6-48b7-b821-91643968b755", + "token_use": "id", + "auth_time": 1615366261, + "name": "Michael Brewer", + "exp": 1615369861, + "iat": 1615366261 + }, + "defaultAuthStrategy": "ALLOW", + "groups": null, + "issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID", + "sourceIp": ["11.215.2.22"], + "sub": "07920713-4526-4642-9c88-2953512de441", + "username": "mike" + }, + "source": { + "name": "Value", + "nested": { + "name": "value", + "list": [] + } + }, + "request": { + "headers": { + "x-forwarded-for": "11.215.2.22, 64.44.173.11", + "cloudfront-viewer-country": "US", + "cloudfront-is-tablet-viewer": "false", + "via": "2.0 SOMETHING.cloudfront.net (CloudFront)", + "cloudfront-forwarded-proto": "https", + "origin": "https://console.aws.amazon.com", + "content-length": "156", + "accept-language": "en-US,en;q=0.9", + "host": "SOMETHING.appsync-api.us-east-1.amazonaws.com", + "x-forwarded-proto": "https", + "sec-gpc": "1", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", + "accept": "*/*", + "cloudfront-is-mobile-viewer": "false", + "cloudfront-is-smarttv-viewer": "false", + "accept-encoding": "gzip, deflate, br", + "referer": "https://console.aws.amazon.com/", + "content-type": "application/json", + "sec-fetch-mode": "cors", + "x-amz-cf-id": "Fo5VIuvP6V6anIEt62WzFDCK45mzM4yEdpt5BYxOl9OFqafd-WR0cA==", + "x-amzn-trace-id": "Root=1-60488877-0b0c4e6727ab2a1c545babd0", + "authorization": "AUTH-HEADER", + "sec-fetch-dest": "empty", + "x-amz-user-agent": "AWS-Console-AppSync/", + "cloudfront-is-desktop-viewer": "true", + "sec-fetch-site": "cross-site", + "x-forwarded-port": "443" + }, + "domainName": "SOMETHING.appsync-api.us-east-1.amazonaws.com" + }, + "prev": { + "result": {} + }, + "info": { + "selectionSetList": ["id", "field1", "field2"], + "selectionSetGraphQL": "{\n id\n field1\n field2\n}", + "parentTypeName": "Merchant", + "fieldName": "locations", + "variables": {} + }, + "stash": {} + } \ No newline at end of file diff --git a/tests/unit/parser/_pydantic/test_appsync.py b/tests/unit/parser/_pydantic/test_appsync.py new file mode 100644 index 00000000000..ef0b86816fd --- /dev/null +++ b/tests/unit/parser/_pydantic/test_appsync.py @@ -0,0 +1,43 @@ +import json +import pathlib +import pytest + +from aws_lambda_powertools.utilities.parser import parse, ValidationError +from aws_lambda_powertools.utilities.parser.models.appsync import AppSyncResolverEventModel + + +def load_event(filename: str) -> dict: + """ + Load a JSON event from the events directory. + + The function navigates four levels up from the current file to locate the + `tests/events` folder. + """ + event_path = pathlib.Path(__file__).parent.parent.parent.parent / "events" / filename + with event_path.open() as f: + return json.load(f) + + +def test_appsync_event_model_parses_successfully(): + """ + Validate that a valid AppSync resolver event is correctly parsed by the model. + """ + event = load_event("appsync_resolver_event.json") + + parsed_event = parse(event=event, model=AppSyncResolverEventModel) + + assert parsed_event.arguments["page"] == 2 + assert parsed_event.identity.username == "mike" + assert parsed_event.request.headers["host"].endswith("appsync-api.us-east-1.amazonaws.com") + assert parsed_event.info.fieldName == "locations" + assert parsed_event.info.parentTypeName == "Merchant" + + +def test_appsync_event_model_invalid_payload_raises(): + """ + Validate that parsing an invalid AppSync resolver event payload raises a ValidationError. + """ + invalid_event = {"invalid": "event"} + + with pytest.raises(ValidationError): + parse(event=invalid_event, model=AppSyncResolverEventModel) From 2e7e53071dee4b545168a5b20f87a0cc232d47ca Mon Sep 17 00:00:00 2001 From: Vatsal Goel <144617902+VatsalGoel3@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:40:19 -0600 Subject: [PATCH 2/5] Updated documentation --- aws_lambda_powertools/utilities/parser/models/__init__.py | 2 +- docs/utilities/parser.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index 917970ab604..7ea8da2dc22 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -29,7 +29,6 @@ ) from .appsync import ( AppSyncResolverEventModel, - AppSyncBatchResolverEventModel, ) from .bedrock_agent import ( BedrockAgentEventModel, @@ -141,6 +140,7 @@ "AlbModel", "AlbRequestContext", "AlbRequestContextData", + "AppSyncResolverEventModel", "DynamoDBStreamModel", "EventBridgeModel", "DynamoDBStreamChangedRecordModel", diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index b6abbe965e1..4cdf0d452f2 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -111,6 +111,7 @@ The example above uses `SqsModel`. Other built-in models can be found below. | **APIGatewayWebSocketMessageEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API message body | | **APIGatewayWebSocketConnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $connect message | | **APIGatewayWebSocketDisconnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $disconnect message | +| **AppSyncResolverEventModel** | Lambda Event Source payload for AWS AppSync Resolver | | **BedrockAgentEventModel** | Lambda Event Source payload for Bedrock Agents | | **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation | | **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation | From 9b9cecab0059a71d41a04db02ca14e64f454f40d Mon Sep 17 00:00:00 2001 From: Vatsal Goel <144617902+VatsalGoel3@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:18:24 -0600 Subject: [PATCH 3/5] Updated test code --- tests/unit/parser/_pydantic/test_appsync.py | 23 ++++----------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/tests/unit/parser/_pydantic/test_appsync.py b/tests/unit/parser/_pydantic/test_appsync.py index ef0b86816fd..bc8bb50c281 100644 --- a/tests/unit/parser/_pydantic/test_appsync.py +++ b/tests/unit/parser/_pydantic/test_appsync.py @@ -1,21 +1,8 @@ -import json -import pathlib import pytest - +import json from aws_lambda_powertools.utilities.parser import parse, ValidationError -from aws_lambda_powertools.utilities.parser.models.appsync import AppSyncResolverEventModel - - -def load_event(filename: str) -> dict: - """ - Load a JSON event from the events directory. - - The function navigates four levels up from the current file to locate the - `tests/events` folder. - """ - event_path = pathlib.Path(__file__).parent.parent.parent.parent / "events" / filename - with event_path.open() as f: - return json.load(f) +from aws_lambda_powertools.utilities.parser.models import AppSyncResolverEventModel +from tests.functional.utils import load_event def test_appsync_event_model_parses_successfully(): @@ -23,7 +10,6 @@ def test_appsync_event_model_parses_successfully(): Validate that a valid AppSync resolver event is correctly parsed by the model. """ event = load_event("appsync_resolver_event.json") - parsed_event = parse(event=event, model=AppSyncResolverEventModel) assert parsed_event.arguments["page"] == 2 @@ -38,6 +24,5 @@ def test_appsync_event_model_invalid_payload_raises(): Validate that parsing an invalid AppSync resolver event payload raises a ValidationError. """ invalid_event = {"invalid": "event"} - with pytest.raises(ValidationError): - parse(event=invalid_event, model=AppSyncResolverEventModel) + parse(event=invalid_event, model=AppSyncResolverEventModel) \ No newline at end of file From dcca673584e6b40717be6974e33763cb08e186df Mon Sep 17 00:00:00 2001 From: Vatsal Goel <144617902+VatsalGoel3@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:26:37 -0600 Subject: [PATCH 4/5] Updated test code --- tests/unit/parser/_pydantic/test_appsync.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/parser/_pydantic/test_appsync.py b/tests/unit/parser/_pydantic/test_appsync.py index bc8bb50c281..90d3f69dfa2 100644 --- a/tests/unit/parser/_pydantic/test_appsync.py +++ b/tests/unit/parser/_pydantic/test_appsync.py @@ -1,5 +1,3 @@ -import pytest -import json from aws_lambda_powertools.utilities.parser import parse, ValidationError from aws_lambda_powertools.utilities.parser.models import AppSyncResolverEventModel from tests.functional.utils import load_event From dc80b3ffd6c48c46a644c06534911a7af7378ca0 Mon Sep 17 00:00:00 2001 From: Vatsal Goel <144617902+VatsalGoel3@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:28:31 -0600 Subject: [PATCH 5/5] Update test code --- tests/unit/parser/_pydantic/test_appsync.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/parser/_pydantic/test_appsync.py b/tests/unit/parser/_pydantic/test_appsync.py index 90d3f69dfa2..b8a57eaa7c3 100644 --- a/tests/unit/parser/_pydantic/test_appsync.py +++ b/tests/unit/parser/_pydantic/test_appsync.py @@ -1,8 +1,9 @@ +import pytest + from aws_lambda_powertools.utilities.parser import parse, ValidationError from aws_lambda_powertools.utilities.parser.models import AppSyncResolverEventModel from tests.functional.utils import load_event - def test_appsync_event_model_parses_successfully(): """ Validate that a valid AppSync resolver event is correctly parsed by the model.