Skip to content

Commit 33b5a9c

Browse files
fix(appsync): enhance consistency for custom resolver field naming in AppSync (#5801)
Fixing an error in AppSync resolver event Co-authored-by: Ana Falcão <[email protected]>
1 parent 0cbd1c1 commit 33b5a9c

File tree

5 files changed

+117
-8
lines changed

5 files changed

+117
-8
lines changed

aws_lambda_powertools/utilities/data_classes/appsync_resolver_event.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ def __init__(self, data: dict):
158158

159159
info: dict | None = data.get("info")
160160
if not info:
161-
info = {"fieldName": self.get("fieldName"), "parentTypeName": self.get("typeName")}
161+
parent_type_name = self.get("parentTypeName") or self.get("typeName")
162+
info = {"fieldName": self.get("fieldName"), "parentTypeName": parent_type_name}
162163

163164
self._info = AppSyncResolverEventInfo(info)
164165

aws_lambda_powertools/utilities/parser/parser.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def handler(event: Order, context: LambdaContext):
110110

111111
logger.debug(f"Calling handler {handler.__name__}")
112112
return handler(parsed_event, context, **kwargs)
113-
113+
114114

115115
@overload
116116
def parse(event: dict[str, Any], model: type[T]) -> T: ... # pragma: no cover
@@ -189,7 +189,7 @@ def handler(event: Order, context: LambdaContext):
189189
adapter = _retrieve_or_set_model_from_cache(model=model)
190190

191191
logger.debug("Parsing and validating event model; no envelope used")
192-
192+
193193
return _parse_and_validate_event(data=event, adapter=adapter)
194194

195195
# Pydantic raises PydanticSchemaGenerationError when the model is not a Pydantic model
@@ -202,4 +202,4 @@ def handler(event: Order, context: LambdaContext):
202202
f"Error: {str(exc)}. Please ensure the Input model inherits from BaseModel,\n"
203203
"and your payload adheres to the specified Input model structure.\n"
204204
f"Model={model}",
205-
) from exc
205+
) from exc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"parentTypeName": "Merchant",
3+
"fieldName": "locations",
4+
"arguments": {
5+
"page": 2
6+
},
7+
"identity": {
8+
"claims": {
9+
"sub": "07920713-4526-4642-9c88-2953512de441",
10+
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
11+
"aud": "58rc9bf5kkti90ctmvioppukm9",
12+
"event_id": "7f4c9383-abf6-48b7-b821-91643968b755",
13+
"token_use": "id",
14+
"auth_time": 1615366261,
15+
"name": "Michael Brewer",
16+
"exp": 1615369861,
17+
"iat": 1615366261
18+
},
19+
"defaultAuthStrategy": "ALLOW",
20+
"groups": null,
21+
"issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
22+
"sourceIp": [
23+
"11.215.2.22"
24+
],
25+
"sub": "07920713-4526-4642-9c88-2953512de441",
26+
"username": "mike"
27+
},
28+
"source": {
29+
"name": "Value",
30+
"nested": {
31+
"name": "value",
32+
"list": []
33+
}
34+
},
35+
"request": {
36+
"headers": {
37+
"x-forwarded-for": "11.215.2.22, 64.44.173.11",
38+
"cloudfront-viewer-country": "US",
39+
"cloudfront-is-tablet-viewer": "false",
40+
"via": "2.0 SOMETHING.cloudfront.net (CloudFront)",
41+
"cloudfront-forwarded-proto": "https",
42+
"origin": "https://console.aws.amazon.com",
43+
"content-length": "156",
44+
"accept-language": "en-US,en;q=0.9",
45+
"host": "SOMETHING.appsync-api.us-east-1.amazonaws.com",
46+
"x-forwarded-proto": "https",
47+
"sec-gpc": "1",
48+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) etc.",
49+
"accept": "*/*",
50+
"cloudfront-is-mobile-viewer": "false",
51+
"cloudfront-is-smarttv-viewer": "false",
52+
"accept-encoding": "gzip, deflate, br",
53+
"referer": "https://console.aws.amazon.com/",
54+
"content-type": "application/json",
55+
"sec-fetch-mode": "cors",
56+
"x-amz-cf-id": "Fo5VIuvP6V6anIEt62WzFDCK45mzM4yEdpt5BYxOl9OFqafd-WR0cA==",
57+
"x-amzn-trace-id": "Root=1-60488877-0b0c4e6727ab2a1c545babd0",
58+
"authorization": "AUTH-HEADER",
59+
"sec-fetch-dest": "empty",
60+
"x-amz-user-agent": "AWS-Console-AppSync/",
61+
"cloudfront-is-desktop-viewer": "true",
62+
"sec-fetch-site": "cross-site",
63+
"x-forwarded-port": "443"
64+
}
65+
},
66+
"prev": {
67+
"result": {}
68+
}
69+
}

tests/functional/event_handler/required_dependencies/appsync/test_appsync_single_resolvers.py

+34
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,40 @@ def create_something(id: str): # noqa AA03 VNE003
2626
assert result == "my identifier"
2727

2828

29+
def test_direct_resolver_with_parent_name():
30+
# Check whether we can handle an example appsync direct resolver
31+
mock_event = load_event("appSyncDirectResolver.json")
32+
33+
app = AppSyncResolver()
34+
35+
@app.resolver(field_name="createSomething", type_name="Mutation")
36+
def create_something(id: str): # noqa AA03 VNE003
37+
assert app.lambda_context == {}
38+
return id
39+
40+
# Call the implicit handler
41+
result = app(mock_event, {})
42+
43+
assert result == "my identifier"
44+
45+
46+
def test_custom_resolver_with_fields():
47+
# Check whether we can handle an example appsync with custom resolver
48+
mock_event = load_event("appSyncCustomResolverEvent.json")
49+
50+
app = AppSyncResolver()
51+
52+
@app.resolver(field_name="locations", type_name="Merchant")
53+
def create_something(page: int): # noqa AA03 VNE003
54+
assert app.lambda_context == {}
55+
return page
56+
57+
# Call the implicit handler
58+
result = app(mock_event, {})
59+
60+
assert result == 2
61+
62+
2963
def test_amplify_resolver():
3064
# Check whether we can handle an example appsync resolver
3165
mock_event = load_event("appSyncResolverEvent.json")

tests/functional/parser/test_parser.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
import pydantic
66
import pytest
7+
from pydantic import BaseModel, ValidationError
78
from typing_extensions import Annotated
8-
from pydantic import ValidationError, BaseModel
99

1010
from aws_lambda_powertools.utilities.parser import event_parser, exceptions, parse
1111
from aws_lambda_powertools.utilities.parser.envelopes.sqs import SqsEnvelope
@@ -130,6 +130,7 @@ def handler(event, _):
130130
with pytest.raises(ValidationError):
131131
handler({"project": "powertools"}, LambdaContext())
132132

133+
133134
def test_parser_validation_error():
134135
class StrictModel(pydantic.BaseModel):
135136
age: int
@@ -143,9 +144,10 @@ def handle_validation(event: Dict, _: LambdaContext):
143144

144145
with pytest.raises(ValidationError) as exc_info:
145146
handle_validation(event=invalid_event, context=LambdaContext())
146-
147+
147148
assert "age" in str(exc_info.value) # Verify the error mentions the invalid field
148149

150+
149151
def test_parser_type_value_errors():
150152
class CustomModel(pydantic.BaseModel):
151153
timestamp: datetime
@@ -158,7 +160,7 @@ def handle_type_validation(event: Dict, _: LambdaContext):
158160
# Test both TypeError and ValueError scenarios
159161
invalid_events = [
160162
{"timestamp": "invalid-date", "status": "SUCCESS"}, # Will raise ValueError for invalid date
161-
{"timestamp": datetime.now(), "status": "INVALID"} # Will raise ValueError for invalid literal
163+
{"timestamp": datetime.now(), "status": "INVALID"}, # Will raise ValueError for invalid literal
162164
]
163165

164166
for invalid_event in invalid_events:
@@ -168,17 +170,19 @@ def handle_type_validation(event: Dict, _: LambdaContext):
168170

169171
def test_event_parser_no_model():
170172
with pytest.raises(exceptions.InvalidModelTypeError):
173+
171174
@event_parser
172175
def handler(event, _):
173176
return event
174-
177+
175178
handler({}, None)
176179

177180

178181
class Shopping(BaseModel):
179182
id: int
180183
description: str
181184

185+
182186
def test_event_parser_invalid_event():
183187
event = {"id": "forgot-the-id", "description": "really nice blouse"} # 'id' is invalid
184188

@@ -228,6 +232,7 @@ def handler(event, _: Any) -> str:
228232
ret = handler(test_input, None)
229233
assert ret == expected
230234

235+
231236
@pytest.mark.parametrize(
232237
"test_input,expected",
233238
[

0 commit comments

Comments
 (0)