Skip to content

Commit 3014dc6

Browse files
refactor(parser): Improve AppSync models with examples and descriptions (#7330)
* refactor(parser): Improve AppSync models with examples and descriptions Enhances the AppSync parser models with field descriptions and examples using Pydantic's Field() functionality. This improvement provides better documentation and metadata for AppSync event parsing, following the pattern established in previous PRs. All field descriptions are based on official AWS AppSync documentation and include realistic examples from actual test events. Closes #7122 * fix: Remove security hotspots from AppSync model examples - Sanitize JWT token examples to use generic placeholders - Remove authorization header from examples to avoid hardcoded credentials - Replace sensitive UUIDs with generic placeholders - Address SonarQube security hotspots while maintaining example clarity * fix: Further sanitize JWT examples to resolve SonarCloud security hotspots - Remove token_use and auth_time fields from JWT claims examples - Sanitize OIDC subject identifier examples - Address remaining SonarCloud security concerns while maintaining example clarity * fix: Simplify authentication examples to resolve SonarCloud security hotspots - Simplify JWT claims examples to minimal required fields - Remove complex nested authentication data - Keep examples clear but security-compliant - Final resolution of SonarCloud security concerns * fix: Remove all JWT claims examples to resolve SonarCloud security hotspots - Remove JWT claims examples that trigger security hotspots - Keep comprehensive field descriptions for developer guidance - Maintain functionality while ensuring security compliance - Final resolution of all SonarCloud security concerns * fix: Replace hardcoded IP addresses with safe examples - Replace all hardcoded IP addresses with localhost and private network examples - Use 127.0.0.1 and 10.0.0.x addresses to avoid SonarCloud security hotspots - Addresses SonarCloud security concerns about hardcoded IP addresses - Final resolution of all security hotspots * fix: Remove all IP address examples to resolve SonarCloud security hotspots - Remove all IP address examples from sourceIp fields and headers - Keep comprehensive field descriptions for developer guidance - Maintain functionality while ensuring complete security compliance - Final resolution of all SonarCloud security hotspots * fix: Apply code formatting to resolve CI quality check failures - Format appsync.py file according to project standards using Ruff - Fix 13 additional linting issues automatically - Ensure all 1167 files pass formatting checks - Resolves GitHub Actions workflow failure in PR #7330 * Fix CI --------- Co-authored-by: Leandro Damascena <lcdama@amazon.pt>
1 parent 439638c commit 3014dc6

File tree

2 files changed

+254
-51
lines changed

2 files changed

+254
-51
lines changed
Lines changed: 187 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,109 @@
11
from typing import Any, Dict, List, Optional, Union
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Field
44

55

66
class AppSyncIamIdentity(BaseModel):
7-
accountId: str
8-
cognitoIdentityPoolId: Optional[str]
9-
cognitoIdentityId: Optional[str]
10-
sourceIp: List[str]
11-
username: str
12-
userArn: str
13-
cognitoIdentityAuthType: Optional[str]
14-
cognitoIdentityAuthProvider: Optional[str]
7+
accountId: str = Field(description="The AWS account ID of the caller.", examples=["123456789012"])
8+
cognitoIdentityPoolId: Optional[str] = Field(
9+
default=None,
10+
description="The Amazon Cognito identity pool ID associated with the caller.",
11+
examples=["us-east-1:12345678-1234-1234-1234-123456789012"],
12+
)
13+
cognitoIdentityId: Optional[str] = Field(
14+
default=None,
15+
description="The Amazon Cognito identity ID of the caller.",
16+
examples=["us-east-1:12345678-1234-1234-1234-123456789012"],
17+
)
18+
sourceIp: List[str] = Field(
19+
description=(
20+
"The source IP address of the caller that AWS AppSync receives. "
21+
"If the request includes a x-forwarded-for header, this is a list of IP addresses."
22+
),
23+
)
24+
username: str = Field(
25+
description="The IAM user principal name.",
26+
examples=["AIDAJEXAMPLE1234", "appsync-user"],
27+
)
28+
userArn: str = Field(
29+
description="The Amazon Resource Name (ARN) of the IAM user.",
30+
examples=["arn:aws:iam::123456789012:user/appsync", "arn:aws:iam::123456789012:user/service-user"],
31+
)
32+
cognitoIdentityAuthType: Optional[str] = Field(
33+
default=None,
34+
description="Either authenticated or unauthenticated based on the identity type.",
35+
examples=["authenticated", "unauthenticated"],
36+
)
37+
cognitoIdentityAuthProvider: Optional[str] = Field(
38+
default=None,
39+
description=(
40+
"A comma-separated list of external identity provider information "
41+
"used in obtaining the credentials used to sign the request."
42+
),
43+
examples=[
44+
"cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
45+
"graph.facebook.com,cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
46+
],
47+
)
1548

1649

1750
class AppSyncCognitoIdentity(BaseModel):
18-
sub: str
19-
issuer: str
20-
username: str
21-
claims: Dict[str, Any]
22-
sourceIp: List[str]
23-
defaultAuthStrategy: str
24-
groups: Optional[List[str]]
51+
sub: str = Field(
52+
description="The UUID of the authenticated user from Cognito User Pool.",
53+
examples=["user-uuid-1234-5678-9012-123456789012", "user-uuid-abcd-efgh-ijkl-mnopqrstuvwx"],
54+
)
55+
issuer: str = Field(
56+
description="The token issuer URL from Cognito User Pool.",
57+
examples=[
58+
"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_POOL_ID",
59+
"https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx",
60+
],
61+
)
62+
username: str = Field(
63+
description="The username of the authenticated user (cognito:username attribute).",
64+
examples=["mike", "jdoe", "user123"],
65+
)
66+
claims: Dict[str, Any] = Field(description="The JWT claims that the user has from Cognito User Pool.")
67+
sourceIp: List[str] = Field(
68+
description=(
69+
"The source IP address of the caller that AWS AppSync receives. "
70+
"If the request includes a x-forwarded-for header, this is a list of IP addresses."
71+
),
72+
)
73+
defaultAuthStrategy: str = Field(
74+
description="The default authorization strategy for this caller (ALLOW or DENY).",
75+
examples=["ALLOW", "DENY"],
76+
)
77+
groups: Optional[List[str]] = Field(
78+
default=None,
79+
description="The Cognito User Pool groups that the user belongs to.",
80+
examples=[["admin", "users"], ["developers"]],
81+
)
2582

2683

2784
class AppSyncOidcIdentity(BaseModel):
28-
claims: Dict[str, Any]
29-
issuer: str
30-
sub: str
85+
claims: Dict[str, Any] = Field(description="The JWT claims from the OpenID Connect provider.")
86+
issuer: str = Field(
87+
description="The token issuer URL from the OpenID Connect provider.",
88+
examples=["https://accounts.google.com", "https://login.microsoftonline.com/tenant-id/v2.0"],
89+
)
90+
sub: str = Field(
91+
description="The subject identifier from the OpenID Connect provider.",
92+
examples=["248289761001", "provider-subject-identifier"],
93+
)
3194

3295

3396
class AppSyncLambdaIdentity(BaseModel):
34-
resolverContext: Dict[str, Any]
97+
resolverContext: Dict[str, Any] = Field(
98+
description=(
99+
"The resolver context returned by the Lambda function authorizing the request. "
100+
"Contains custom authorization data from AWS_LAMBDA authorization."
101+
),
102+
examples=[
103+
{"userId": "user123", "role": "admin", "permissions": ["read", "write"]},
104+
{"customClaim": "value", "authLevel": "premium"},
105+
],
106+
)
35107

36108

37109
AppSyncIdentity = Union[
@@ -43,30 +115,110 @@ class AppSyncLambdaIdentity(BaseModel):
43115

44116

45117
class AppSyncRequestModel(BaseModel):
46-
domainName: Optional[str]
47-
headers: Dict[str, str]
118+
domainName: Optional[str] = Field(
119+
default=None,
120+
description=(
121+
"The custom domain name used to access the GraphQL endpoint. "
122+
"Returns null when using the default GraphQL endpoint domain name."
123+
),
124+
examples=["api.example.com", "graphql.mycompany.com"],
125+
)
126+
headers: Dict[str, str] = Field(
127+
description="HTTP headers from the GraphQL request, including custom headers.",
128+
examples=[
129+
{
130+
"cloudfront-viewer-country": "US",
131+
"host": "example.appsync-api.us-east-1.amazonaws.com",
132+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
133+
"content-type": "application/json",
134+
},
135+
],
136+
)
48137

49138

50139
class AppSyncInfoModel(BaseModel):
51-
selectionSetList: List[str]
52-
selectionSetGraphQL: str
53-
parentTypeName: str
54-
fieldName: str
55-
variables: Dict[str, Any]
140+
selectionSetList: List[str] = Field(
141+
description=(
142+
"A list representation of the fields in the GraphQL selection set. "
143+
"Fields that are aliased are referenced only by the alias name."
144+
),
145+
examples=[["id", "field1", "field2"], ["postId", "title", "content", "author", "author/id", "author/name"]],
146+
)
147+
selectionSetGraphQL: str = Field(
148+
description=(
149+
"A string representation of the selection set, formatted as GraphQL SDL. "
150+
"Inline fragments are preserved but fragments are not merged."
151+
),
152+
examples=[
153+
"{\n id\n field1\n field2\n}",
154+
"{\n postId\n title\n content\n author {\n id\n name\n }\n}",
155+
],
156+
)
157+
parentTypeName: str = Field(
158+
description="The name of the parent type for the field that is currently being resolved.",
159+
examples=["Query", "Mutation", "Subscription", "User", "Post"],
160+
)
161+
fieldName: str = Field(
162+
description="The name of the field that is currently being resolved.",
163+
examples=["getUser", "createPost", "locations", "updateProfile"],
164+
)
165+
variables: Dict[str, Any] = Field(
166+
description="A map which holds all variables that are passed into the GraphQL request.",
167+
examples=[{"userId": "123", "limit": 10}, {"input": {"name": "John", "email": "john@example.com"}}, {}],
168+
)
56169

57170

58171
class AppSyncPrevModel(BaseModel):
59-
result: Dict[str, Any]
172+
result: Dict[str, Any] = Field(
173+
description=(
174+
"The result of whatever previous operation was executed in a pipeline resolver. "
175+
"Contains the output from the previous function or Before mapping template."
176+
),
177+
examples=[
178+
{"userId": "123", "posts": [{"id": "1", "title": "Hello World"}]},
179+
{"data": {"field1": "value1", "field2": "value2"}},
180+
],
181+
)
60182

61183

62184
class AppSyncResolverEventModel(BaseModel):
63-
arguments: Dict[str, Any]
64-
identity: Optional[AppSyncIdentity]
65-
source: Optional[Dict[str, Any]]
66-
request: AppSyncRequestModel
67-
info: AppSyncInfoModel
68-
prev: Optional[AppSyncPrevModel]
69-
stash: Dict[str, Any]
185+
arguments: Dict[str, Any] = Field(
186+
description="The arguments passed to the GraphQL field.",
187+
examples=[
188+
{"id": "123", "limit": 10},
189+
{"input": {"name": "John", "email": "john@example.com"}},
190+
{"page": 2, "size": 1, "name": "value"},
191+
],
192+
)
193+
identity: Optional[AppSyncIdentity] = Field(
194+
default=None,
195+
description="Information about the caller identity (authenticated user or API key).",
196+
)
197+
source: Optional[Dict[str, Any]] = Field(
198+
default=None,
199+
description="The parent object for the field. For top-level fields, this will be null.",
200+
examples=[
201+
None,
202+
{"id": "user123", "name": "John Doe"},
203+
{"name": "Value", "nested": {"name": "value", "list": []}},
204+
{"postId": "post456", "title": "My Post"},
205+
],
206+
)
207+
request: AppSyncRequestModel = Field(description="Information about the GraphQL request context.")
208+
info: AppSyncInfoModel = Field(
209+
description="Information about the GraphQL request including selection set and field details.",
210+
)
211+
prev: Optional[AppSyncPrevModel] = Field(
212+
default=None,
213+
description="Results from the previous resolver in a pipeline resolver.",
214+
)
215+
stash: Dict[str, Any] = Field(
216+
description=(
217+
"The stash is a map that is made available inside each resolver and function mapping template. "
218+
"The same stash instance lives through a single resolver execution."
219+
),
220+
examples=[{"customData": "value", "userId": "123"}],
221+
)
70222

71223

72224
AppSyncBatchResolverEventModel = List[AppSyncResolverEventModel]
Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,86 @@
11
from typing import Any, Dict, List, Literal, Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Field
44

55
from aws_lambda_powertools.utilities.parser.models.appsync import AppSyncIdentity, AppSyncRequestModel
66

77

88
class AppSyncEventsInfoChannelModel(BaseModel):
9-
path: str
10-
segments: List[str]
9+
path: str = Field(
10+
description="The full path of the AppSync Events channel.",
11+
examples=["/default/channel", "/notifications/user-updates", "/chat/room-123"],
12+
)
13+
segments: List[str] = Field(
14+
description="The path segments of the channel, split by forward slashes.",
15+
examples=[["default", "channel"], ["notifications", "user-updates"], ["chat", "room-123"]],
16+
)
1117

1218

1319
class AppSyncEventsInfoChannelNamespaceModel(BaseModel):
14-
name: str
20+
name: str = Field(
21+
description="The namespace name for the AppSync Events channel.",
22+
examples=["default", "notifications", "chat", "user-events"],
23+
)
1524

1625

1726
class AppSyncEventsInfoModel(BaseModel):
18-
channel: AppSyncEventsInfoChannelModel
19-
channelNamespace: AppSyncEventsInfoChannelNamespaceModel
20-
operation: Literal["PUBLISH", "SUBSCRIBE"]
27+
channel: AppSyncEventsInfoChannelModel = Field(description="Information about the AppSync Events channel.")
28+
channelNamespace: AppSyncEventsInfoChannelNamespaceModel = Field(
29+
description="The namespace information for the channel.",
30+
)
31+
operation: Literal["PUBLISH", "SUBSCRIBE"] = Field(
32+
description="The type of operation being performed on the channel.",
33+
examples=["PUBLISH", "SUBSCRIBE"],
34+
)
2135

2236

2337
class AppSyncEventsEventModel(BaseModel):
24-
id: str
25-
payload: Dict[str, Any]
38+
id: str = Field(
39+
description="The unique identifier for the event.",
40+
examples=["1", "2", "event-123", "notification-456"],
41+
)
42+
payload: Dict[str, Any] = Field(
43+
description="The event data payload containing the actual event information.",
44+
examples=[
45+
{"event_1": "data_1"},
46+
{"event_2": "data_2"},
47+
{"userId": "123", "action": "login", "timestamp": "2023-01-01T00:00:00Z"},
48+
{"message": "Hello World", "type": "notification"},
49+
],
50+
)
2651

2752

2853
class AppSyncEventsModel(BaseModel):
29-
identity: Optional[AppSyncIdentity] = None
30-
request: AppSyncRequestModel
31-
info: AppSyncEventsInfoModel
32-
prev: Optional[str] = None
33-
outErrors: Optional[List[str]] = None
34-
stash: Optional[Dict[str, Any]] = None
35-
events: Optional[List[AppSyncEventsEventModel]] = None
54+
identity: Optional[AppSyncIdentity] = Field(
55+
default=None,
56+
description="Information about the caller identity (authenticated user or API key).",
57+
)
58+
request: AppSyncRequestModel = Field(description="Information about the GraphQL request context.")
59+
info: AppSyncEventsInfoModel = Field(
60+
description="Information about the AppSync Events operation including channel details.",
61+
)
62+
prev: Optional[str] = Field(
63+
default=None,
64+
description="Results from the previous operation in a pipeline resolver.",
65+
examples=["previous-result-data"],
66+
)
67+
outErrors: Optional[List[str]] = Field(
68+
default=None,
69+
description="List of output errors that occurred during event processing.",
70+
examples=[["Error message 1", "Error message 2"]],
71+
)
72+
stash: Optional[Dict[str, Any]] = Field(
73+
default=None,
74+
description=(
75+
"The stash is a map that is made available inside each resolver and function mapping template. "
76+
"The same stash instance lives through a single resolver execution."
77+
),
78+
examples=[{"customData": "value", "userId": "123"}],
79+
)
80+
events: Optional[List[AppSyncEventsEventModel]] = Field(
81+
default=None,
82+
description="List of events being published or subscribed to in the AppSync Events operation.",
83+
examples=[
84+
[{"id": "1", "payload": {"event_1": "data_1"}}, {"id": "2", "payload": {"event_2": "data_2"}}],
85+
],
86+
)

0 commit comments

Comments
 (0)