geek-cafe-saas-sdk 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of geek-cafe-saas-sdk might be problematic. Click here for more details.
- geek_cafe_saas_sdk/__init__.py +9 -0
- geek_cafe_saas_sdk/core/__init__.py +11 -0
- geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
- geek_cafe_saas_sdk/core/error_codes.py +132 -0
- geek_cafe_saas_sdk/core/service_errors.py +19 -0
- geek_cafe_saas_sdk/core/service_result.py +121 -0
- geek_cafe_saas_sdk/decorators/__init__.py +64 -0
- geek_cafe_saas_sdk/decorators/auth.py +373 -0
- geek_cafe_saas_sdk/decorators/core.py +358 -0
- geek_cafe_saas_sdk/domains/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
- geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
- geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
- geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
- geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
- geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
- geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
- geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
- geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
- geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
- geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
- geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
- geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
- geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
- geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
- geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
- geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
- geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
- geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
- geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
- geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
- geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
- geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
- geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
- geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
- geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
- geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
- geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
- geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
- geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
- geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
- geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
- geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
- geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
- geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
- geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
- geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
- geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
- geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
- geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
- geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
- geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
- geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
- geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
- geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
- geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
- geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
- geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
- geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
- geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
- geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
- geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
- geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
- geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
- geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
- geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
- geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
- geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
- geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
- geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
- geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
- geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
- geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
- geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
- geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
- geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
- geek_cafe_saas_sdk/middleware/__init__.py +36 -0
- geek_cafe_saas_sdk/middleware/auth.py +85 -0
- geek_cafe_saas_sdk/middleware/authorization.py +523 -0
- geek_cafe_saas_sdk/middleware/cors.py +63 -0
- geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
- geek_cafe_saas_sdk/middleware/validation.py +80 -0
- geek_cafe_saas_sdk/models/__init__.py +20 -0
- geek_cafe_saas_sdk/models/base_model.py +233 -0
- geek_cafe_saas_sdk/services/__init__.py +18 -0
- geek_cafe_saas_sdk/services/database_service.py +441 -0
- geek_cafe_saas_sdk/utilities/__init__.py +88 -0
- geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
- geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
- geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
- geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
- geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
- geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
- geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
- geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
- geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
- geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
- geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
- geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
- geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
- geek_cafe_saas_sdk/utilities/response.py +209 -0
- geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
- geek_cafe_saas_sdk-0.6.0.dist-info/licenses/LICENSE +47 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
"""Lambda Event Utilities
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for working with AWS Lambda event payloads.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Dict, Any, Optional, Union
|
|
7
|
+
import re
|
|
8
|
+
import json
|
|
9
|
+
from aws_lambda_powertools import Logger
|
|
10
|
+
from geek_cafe_saas_sdk.utilities.custom_exceptions import Error
|
|
11
|
+
from geek_cafe_saas_sdk.utilities.http_status_code import HttpStatusCodes
|
|
12
|
+
from geek_cafe_saas_sdk.utilities.environment_variables import (
|
|
13
|
+
EnvironmentVariables,
|
|
14
|
+
)
|
|
15
|
+
from geek_cafe_saas_sdk.utilities.jwt_utility import JwtUtility
|
|
16
|
+
|
|
17
|
+
from boto3_assist.utilities.serialization_utility import JsonConversions
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = Logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
TABLE_NAME_NOT_AVAILABLE = "FAKE_TABLE_ENVIRONMENT_VAR_IS_NOT_SET"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LambdaEventUtility:
|
|
27
|
+
"""Utility class for extracting and processing data from AWS Lambda event payloads.
|
|
28
|
+
|
|
29
|
+
This class provides static methods to extract common data elements from Lambda events,
|
|
30
|
+
including user information, path parameters, query parameters, and more.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def get_dynamodb_table_name() -> str | None:
|
|
35
|
+
"""Get the application DynamoDB table name from environment variables.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
str | None: The DynamoDB table name or TABLE_NAME_NOT_AVAILABLE if not set.
|
|
39
|
+
Returns None if the value is not a string.
|
|
40
|
+
"""
|
|
41
|
+
value = EnvironmentVariables.get_dynamodb_table_name()
|
|
42
|
+
if value is None:
|
|
43
|
+
raise Error("The DynamoDB table name is not set")
|
|
44
|
+
if not isinstance(value, str):
|
|
45
|
+
raise Error("The DynamoDB table name is not a string")
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def get_file_name_from_event(event: Dict[str, Any]) -> Optional[str]:
|
|
52
|
+
"""Extract the file name from the event payload.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
event: The Lambda event dictionary
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The file name as a string, or None if not found or not a string
|
|
59
|
+
"""
|
|
60
|
+
value = LambdaEventUtility.get_value_from_event(event, "file_name")
|
|
61
|
+
if not isinstance(value, str):
|
|
62
|
+
return None
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def get_file_type_from_event(event: Dict[str, Any]) -> Optional[str]:
|
|
67
|
+
"""Extract the file type from the event payload.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
event: The Lambda event dictionary
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The file type as a string, or None if not found or not a string
|
|
74
|
+
"""
|
|
75
|
+
value = LambdaEventUtility.get_value_from_event(event, "file_type")
|
|
76
|
+
if not isinstance(value, str):
|
|
77
|
+
return None
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def get_method_type_from_event(event: Dict[str, Any]) -> Optional[str]:
|
|
82
|
+
"""Extract the HTTP method type from the event payload.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
event: The Lambda event dictionary
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The HTTP method (GET, POST, PUT, DELETE, etc.) as a string, or None if not found or not a string
|
|
89
|
+
"""
|
|
90
|
+
value = LambdaEventUtility.get_value_from_event(event, "method_type")
|
|
91
|
+
if not isinstance(value, str):
|
|
92
|
+
return None
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def get_token_use(event: Dict[str, Any]) -> Optional[str]:
|
|
97
|
+
"""Extract the token use information from the event payload.
|
|
98
|
+
|
|
99
|
+
Retrieves the token_use claim from the authorizer context, which indicates
|
|
100
|
+
whether the token is an 'id', 'access', or other type of token.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
event: The Lambda event dictionary
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The token use value as a string, or None if not found or not a string
|
|
107
|
+
"""
|
|
108
|
+
path = "requestContext/authorizer/claims/token_use"
|
|
109
|
+
value = LambdaEventUtility.get_value_from_event(event, path)
|
|
110
|
+
|
|
111
|
+
if not isinstance(value, str):
|
|
112
|
+
return None
|
|
113
|
+
return value
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def get_target_user_id(event: Dict[str, Any]) -> Optional[str]:
|
|
117
|
+
"""Extract the target user ID from the event path parameters.
|
|
118
|
+
|
|
119
|
+
Target users are the users we are performing an action on, such as CRUD operations.
|
|
120
|
+
The user ID is typically found in the route path parameters.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
event: The Lambda event dictionary
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The target user ID as a string, or None if not found or not a string
|
|
127
|
+
"""
|
|
128
|
+
value = LambdaEventUtility.get_value_from_path_parameters(event, "user-id")
|
|
129
|
+
if not isinstance(value, str):
|
|
130
|
+
return None
|
|
131
|
+
return value
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def get_target_tenant_id(event: Dict[str, Any]) -> Optional[str]:
|
|
135
|
+
"""Extract the target tenant ID from the event path parameters.
|
|
136
|
+
|
|
137
|
+
Target tenants are the tenants we are performing an action on, such as CRUD operations.
|
|
138
|
+
The tenant ID is typically found in the route path parameters.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
event: The Lambda event dictionary
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
The target tenant ID as a string, or None if not found or not a string
|
|
145
|
+
"""
|
|
146
|
+
value = LambdaEventUtility.get_value_from_path_parameters(event, "tenant-id")
|
|
147
|
+
if not isinstance(value, str):
|
|
148
|
+
return None
|
|
149
|
+
return value
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def get_authenticated_user_id(event: Dict[str, Any]) -> Optional[str]:
|
|
153
|
+
"""Extract the authenticated user ID from the event claims.
|
|
154
|
+
|
|
155
|
+
Retrieves the user ID from the custom:user_id claim in the authorizer context.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
event: The Lambda event dictionary
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The authenticated user ID as a string, or None if not found or not a string
|
|
162
|
+
"""
|
|
163
|
+
return LambdaEventUtility.get_claims_data(
|
|
164
|
+
event, "custom:user_id"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def get_authenticated_user_email(event: Dict[str, Any]) -> Optional[str]:
|
|
169
|
+
"""Extract the authenticated user email from the event claims.
|
|
170
|
+
|
|
171
|
+
Retrieves the user email from the email claim in the authorizer context.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
event: The Lambda event dictionary
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
The authenticated user email as a string, or None if not found or not a string
|
|
178
|
+
"""
|
|
179
|
+
return LambdaEventUtility.get_claims_data(event, "email")
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def get_authenticated_user_tenant_id(event: Dict[str, Any]) -> Optional[str]:
|
|
183
|
+
"""Extract the authenticated user's tenant ID from the event claims.
|
|
184
|
+
|
|
185
|
+
Retrieves the tenant ID from the custom:tenant_id claim in the authorizer context.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
event: The Lambda event dictionary
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
The authenticated user's tenant ID as a string, or None if not found or not a string
|
|
192
|
+
"""
|
|
193
|
+
return LambdaEventUtility.get_claims_data(
|
|
194
|
+
event, "custom:tenant_id"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def get_authenticated_user_roles(event: Dict[str, Any]) -> List[str]:
|
|
199
|
+
"""Extract the authenticated user's roles from the event claims.
|
|
200
|
+
|
|
201
|
+
Retrieves the user roles from the custom:user_roles claim in the authorizer context.
|
|
202
|
+
The roles are returned as a comma-separated string and converted to a list.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
event: The Lambda event dictionary
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
A list of role strings, or an empty list if no roles are found
|
|
209
|
+
"""
|
|
210
|
+
roles = LambdaEventUtility.get_claims_data(
|
|
211
|
+
event, "custom:user_roles"
|
|
212
|
+
)
|
|
213
|
+
if roles:
|
|
214
|
+
return str(roles).split(",")
|
|
215
|
+
|
|
216
|
+
return []
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def get_authenticated_user_context(event: Dict[str, Any]) -> tuple[Optional[str], Optional[str], List[str]]:
|
|
220
|
+
"""Extract the authenticated user's context (tenant_id, user_id, roles) from the event.
|
|
221
|
+
|
|
222
|
+
Convenience method that retrieves all three common authentication values
|
|
223
|
+
from the authorizer context in a single call.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
event: The Lambda event dictionary
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
A tuple containing (tenant_id, user_id, roles)
|
|
230
|
+
- tenant_id: The user's tenant ID (or None)
|
|
231
|
+
- user_id: The user's ID (or None)
|
|
232
|
+
- roles: A list of role strings (or empty list)
|
|
233
|
+
"""
|
|
234
|
+
tenant_id = LambdaEventUtility.get_authenticated_user_tenant_id(event)
|
|
235
|
+
user_id = LambdaEventUtility.get_authenticated_user_id(event)
|
|
236
|
+
roles = LambdaEventUtility.get_authenticated_user_roles(event)
|
|
237
|
+
|
|
238
|
+
return tenant_id, user_id, roles
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def get_claims(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
242
|
+
"""Extract the complete claims dictionary from the event.
|
|
243
|
+
|
|
244
|
+
Retrieves all claims from the authorizer context in the request context.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
event: The Lambda event dictionary
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
The claims dictionary, or None if not found or invalid format
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ValueError: If the event payload is empty
|
|
254
|
+
"""
|
|
255
|
+
if not event or len(event) == 0:
|
|
256
|
+
raise ValueError("The event payload is empty")
|
|
257
|
+
|
|
258
|
+
path = "requestContext/authorizer/claims"
|
|
259
|
+
value = LambdaEventUtility.get_value_from_event(event, path)
|
|
260
|
+
if isinstance(value, str):
|
|
261
|
+
value = None
|
|
262
|
+
return value
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def get_claims_data(event: Dict[str, Any], key: str) -> Optional[str]:
|
|
266
|
+
"""Extract a specific claim value from the event claims.
|
|
267
|
+
|
|
268
|
+
First attempts to find the claim in the authorizer context, then falls back to
|
|
269
|
+
extracting it from the JWT token if available.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
event: The Lambda event dictionary
|
|
273
|
+
key: The claim key to extract
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
The claim value as a string, or None if not found
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
ValueError: If the event payload is empty
|
|
280
|
+
Error: If the claim cannot be found or if there's an error processing the JWT token
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
if not event or len(event) == 0:
|
|
284
|
+
raise ValueError("The event payload is empty")
|
|
285
|
+
|
|
286
|
+
path = f"requestContext/authorizer/claims/{key}"
|
|
287
|
+
value = LambdaEventUtility.get_value_from_event(event, path)
|
|
288
|
+
|
|
289
|
+
if not value:
|
|
290
|
+
value = LambdaEventUtility.get_value_from_token(event, key)
|
|
291
|
+
|
|
292
|
+
if value:
|
|
293
|
+
logger.debug(
|
|
294
|
+
{
|
|
295
|
+
"message": f"Found key in JWT with {key}",
|
|
296
|
+
f"{key}": value,
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
if isinstance(value, str):
|
|
300
|
+
return value
|
|
301
|
+
else:
|
|
302
|
+
return str(value)
|
|
303
|
+
else:
|
|
304
|
+
raise Error(
|
|
305
|
+
message=f"Failed to locate {key} info in JWT Token",
|
|
306
|
+
status_code=HttpStatusCodes.HTTP_404_NOT_FOUND.value
|
|
307
|
+
)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
raise Error(
|
|
310
|
+
message=f"Failed to locate {key} info in JWT Token",
|
|
311
|
+
status_code=HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
|
|
312
|
+
details=str(e)
|
|
313
|
+
) from e
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
def get_value_from_token(event: Dict[str, Any], key: str) -> Optional[str]:
|
|
317
|
+
"""Extract a specific claim value directly from the JWT token in the Authorization header.
|
|
318
|
+
|
|
319
|
+
This is used as a fallback when the claim is not available in the authorizer context.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
event: The Lambda event dictionary
|
|
323
|
+
key: The claim key to extract from the token
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
The claim value as a string, or None if not found or if the token is invalid
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
ValueError: If the event payload is empty
|
|
330
|
+
"""
|
|
331
|
+
try:
|
|
332
|
+
if not event or len(event) == 0:
|
|
333
|
+
raise ValueError("The event payload is empty")
|
|
334
|
+
|
|
335
|
+
path = "headers/Authorization"
|
|
336
|
+
jwt_token = LambdaEventUtility.get_value_from_event(event, path)
|
|
337
|
+
value: Optional[str] = None
|
|
338
|
+
if jwt_token and isinstance(jwt_token, str):
|
|
339
|
+
# Use the generic JWT utility to parse the token
|
|
340
|
+
value = JwtUtility.get_claim_from_token(jwt_token, key)
|
|
341
|
+
logger.debug(f"Extracted claim '{key}' from JWT token: {value is not None}")
|
|
342
|
+
|
|
343
|
+
if value:
|
|
344
|
+
return value
|
|
345
|
+
|
|
346
|
+
else:
|
|
347
|
+
raise Error(
|
|
348
|
+
{
|
|
349
|
+
"status_code": HttpStatusCodes.HTTP_404_NOT_FOUND.value,
|
|
350
|
+
"message": f"Failed to locate {key} info it JWT Token",
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
except Exception as e:
|
|
354
|
+
raise Error(
|
|
355
|
+
{
|
|
356
|
+
"status_code": HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
|
|
357
|
+
"message": f"Failed to locate {key} info it JWT Token",
|
|
358
|
+
"exception": str(e),
|
|
359
|
+
}
|
|
360
|
+
) from e
|
|
361
|
+
|
|
362
|
+
@staticmethod
|
|
363
|
+
def get_user_email_from_event(event) -> str | None:
|
|
364
|
+
"""Get the user email from the event for claims"""
|
|
365
|
+
if not event:
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
token_use = LambdaEventUtility.get_token_use(event)
|
|
369
|
+
path: str | None = None
|
|
370
|
+
if token_use == "id":
|
|
371
|
+
path = "requestContext/authorizer/claims/email"
|
|
372
|
+
|
|
373
|
+
elif token_use == "access":
|
|
374
|
+
path = "requestContext/authorizer/claims/client_id"
|
|
375
|
+
|
|
376
|
+
value: str | None = None
|
|
377
|
+
if path:
|
|
378
|
+
value = str(LambdaEventUtility.get_value_from_event(event, path))
|
|
379
|
+
else:
|
|
380
|
+
value = LambdaEventUtility.get_claims_data(event, "email")
|
|
381
|
+
|
|
382
|
+
if not isinstance(value, str):
|
|
383
|
+
return None
|
|
384
|
+
return value
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def get_message_id_from_event(event: dict, index: int = 0) -> str | None:
|
|
388
|
+
"""Gets the message id from an event"""
|
|
389
|
+
records = event.get("Records")
|
|
390
|
+
if records:
|
|
391
|
+
item: dict = records[index]
|
|
392
|
+
value = item.get("messageId")
|
|
393
|
+
if not isinstance(value, str):
|
|
394
|
+
return None
|
|
395
|
+
return value
|
|
396
|
+
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@staticmethod
|
|
403
|
+
def get_http_method_from_event(event: Dict[str, Any]) -> Optional[str]:
|
|
404
|
+
"""Extract the HTTP method from the event.
|
|
405
|
+
|
|
406
|
+
Returns the HTTP method (e.g., GET, POST, PUT, DELETE) from the event.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
event: The Lambda event dictionary
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
The HTTP method as a string, or None if not found or invalid format
|
|
413
|
+
"""
|
|
414
|
+
value = LambdaEventUtility.get_value_from_event_ex(event, "httpMethod")
|
|
415
|
+
if not isinstance(value, str):
|
|
416
|
+
return None
|
|
417
|
+
return value
|
|
418
|
+
|
|
419
|
+
@staticmethod
|
|
420
|
+
def get_resource_path_from_event(event: Dict[str, Any]) -> Optional[str]:
|
|
421
|
+
"""Extract the resource path from the event.
|
|
422
|
+
|
|
423
|
+
Returns the actual resource path (route) from the event.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
event: The Lambda event dictionary
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
The resource path as a string, or None if not found or invalid format
|
|
430
|
+
"""
|
|
431
|
+
value = LambdaEventUtility.get_value_from_event_ex(event, "path")
|
|
432
|
+
if not isinstance(value, str):
|
|
433
|
+
return None
|
|
434
|
+
return value
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def get_resource_pattern_from_event(event: Dict[str, Any]) -> Optional[str]:
|
|
438
|
+
"""Extract the resource pattern from the event.
|
|
439
|
+
|
|
440
|
+
Returns the resource path with placeholders instead of actual values.
|
|
441
|
+
For example, instead of returning:
|
|
442
|
+
"/tenants/d46814e8-d061-7036-3c81-e37152773912/subscriptions/8925f80c344f0096a5fe6bfb159cdd370ad888c7d914bb6568797701003f155b"
|
|
443
|
+
|
|
444
|
+
it returns:
|
|
445
|
+
"/tenants/{tenant-id}/subscriptions/{subscription-id}"
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
event: The Lambda event dictionary
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
The resource pattern as a string, or None if not found or invalid format
|
|
452
|
+
"""
|
|
453
|
+
value = LambdaEventUtility.get_value_from_event_ex(event, "resourcePath")
|
|
454
|
+
if not isinstance(value, str):
|
|
455
|
+
return None
|
|
456
|
+
return value
|
|
457
|
+
|
|
458
|
+
@staticmethod
|
|
459
|
+
def get_value_from_event_ex(event: Dict[str, Any], key: str) -> Union[str, Dict[str, Any], None]:
|
|
460
|
+
"""Get a value from the event, checking both direct key and requestContext path.
|
|
461
|
+
|
|
462
|
+
This is an extended version of get_value_from_event that also checks in the requestContext
|
|
463
|
+
if the value is not found in the main event object.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
event: The Lambda event dictionary
|
|
467
|
+
key: The key to look for in the event
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
The value if found, or None if not found
|
|
471
|
+
"""
|
|
472
|
+
value = LambdaEventUtility.get_value_from_event(event, key)
|
|
473
|
+
if not value:
|
|
474
|
+
key = f"requestContext/{key}"
|
|
475
|
+
value = LambdaEventUtility.get_value_from_event(event, key)
|
|
476
|
+
return value
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@staticmethod
|
|
485
|
+
def get_value_from_event(
|
|
486
|
+
event: Dict[str, Any],
|
|
487
|
+
key: str | List[str],
|
|
488
|
+
) -> str | Dict[str, Any] | None:
|
|
489
|
+
"""
|
|
490
|
+
get the value from the event payload
|
|
491
|
+
"""
|
|
492
|
+
logger.debug({"source": "get_value_from_event", "event": event, "key": key})
|
|
493
|
+
|
|
494
|
+
if not event:
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
if "event" in event:
|
|
498
|
+
event = event["event"]
|
|
499
|
+
|
|
500
|
+
if not key:
|
|
501
|
+
logger.warning(
|
|
502
|
+
{
|
|
503
|
+
"source": "get_value_from_event",
|
|
504
|
+
"warning": "missing key for lookup",
|
|
505
|
+
"event": event,
|
|
506
|
+
"key": key,
|
|
507
|
+
}
|
|
508
|
+
)
|
|
509
|
+
return None
|
|
510
|
+
|
|
511
|
+
if "/" in key:
|
|
512
|
+
key = str(key).split("/")
|
|
513
|
+
|
|
514
|
+
elif "." in key:
|
|
515
|
+
key = str(key).split(".")
|
|
516
|
+
|
|
517
|
+
if isinstance(key, str):
|
|
518
|
+
if key in event:
|
|
519
|
+
return event[key]
|
|
520
|
+
else:
|
|
521
|
+
logger.debug(f'key "{key}" is not in event, checking body.')
|
|
522
|
+
|
|
523
|
+
return LambdaEventUtility.get_value_from_event_body(event, key)
|
|
524
|
+
elif isinstance(key, list):
|
|
525
|
+
# loop through
|
|
526
|
+
value: str | Dict[str, Any] | None = event
|
|
527
|
+
for k in key:
|
|
528
|
+
if isinstance(value, dict):
|
|
529
|
+
value = LambdaEventUtility.get_value_from_event(value, k)
|
|
530
|
+
if not value:
|
|
531
|
+
break
|
|
532
|
+
|
|
533
|
+
return value
|
|
534
|
+
|
|
535
|
+
return None
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
@staticmethod
|
|
540
|
+
def get_value_from_header(event, key, default=None) -> str | dict | None:
|
|
541
|
+
value = LambdaEventUtility.search_payload_for_container_element(
|
|
542
|
+
event, "headers", key, default
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
return value
|
|
546
|
+
|
|
547
|
+
@staticmethod
|
|
548
|
+
def get_value_from_path_parameters(event, key, default=None) -> str | dict | None:
|
|
549
|
+
value = LambdaEventUtility.search_payload_for_container_element(
|
|
550
|
+
event, "pathParameters", key, default
|
|
551
|
+
)
|
|
552
|
+
if value is None:
|
|
553
|
+
path = LambdaEventUtility.get_resource_path_from_event(event=event)
|
|
554
|
+
pattern = LambdaEventUtility.get_resource_pattern_from_event(event=event)
|
|
555
|
+
if path and pattern:
|
|
556
|
+
value = LambdaEventUtility.extract_value_from_path(
|
|
557
|
+
path=path, pattern=pattern, variable_name=key
|
|
558
|
+
)
|
|
559
|
+
return value
|
|
560
|
+
|
|
561
|
+
@staticmethod
|
|
562
|
+
def extract_value_from_path(
|
|
563
|
+
path: str, pattern: str, variable_name: str
|
|
564
|
+
) -> str | None:
|
|
565
|
+
# Convert the pattern into a regex pattern to match the variable placeholders
|
|
566
|
+
regex_pattern = re.sub(
|
|
567
|
+
r"\{([^}]+)\}",
|
|
568
|
+
lambda m: f"(?P<{m.group(1).replace('-', '_')}>[^/]+)",
|
|
569
|
+
pattern,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# Replace hyphens with underscores in the variable name for matching
|
|
573
|
+
variable_name = variable_name.replace("-", "_")
|
|
574
|
+
|
|
575
|
+
# Use regex to match the path against the pattern
|
|
576
|
+
match = re.match(regex_pattern, path)
|
|
577
|
+
|
|
578
|
+
if match:
|
|
579
|
+
# Extract the value corresponding to the variable_name
|
|
580
|
+
try:
|
|
581
|
+
return match.group(variable_name)
|
|
582
|
+
except: # noqa: E722, pylint: disable=W0702
|
|
583
|
+
pass
|
|
584
|
+
# else:
|
|
585
|
+
# raise ValueError(f"No match found for the variable '{variable_name}' in the provided path and pattern.")
|
|
586
|
+
return None
|
|
587
|
+
|
|
588
|
+
@staticmethod
|
|
589
|
+
def get_value_from_query_string_parameters(
|
|
590
|
+
event, key, default=None
|
|
591
|
+
) -> str | dict | None:
|
|
592
|
+
value = LambdaEventUtility.search_payload_for_container_element(
|
|
593
|
+
event, "queryStringParameters", key, default
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
return value
|
|
597
|
+
|
|
598
|
+
@staticmethod
|
|
599
|
+
def get_value_from_multi_value_query_string_parameters(
|
|
600
|
+
event, key, default=None
|
|
601
|
+
) -> str | dict | None:
|
|
602
|
+
value = LambdaEventUtility.search_payload_for_container_element(
|
|
603
|
+
event, "multiValueQueryStringParameters", key, default
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
return value
|
|
607
|
+
|
|
608
|
+
@staticmethod
|
|
609
|
+
def search_payload_for_container_element(
|
|
610
|
+
event, container, key, default=None
|
|
611
|
+
) -> str | dict | None:
|
|
612
|
+
logger.debug(
|
|
613
|
+
{
|
|
614
|
+
"action": "search_payload_for_container_element",
|
|
615
|
+
"event": event,
|
|
616
|
+
"container": container,
|
|
617
|
+
"key": key,
|
|
618
|
+
"metric_filter": "search_payload_for_container_element",
|
|
619
|
+
}
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
events = []
|
|
623
|
+
events.append(event)
|
|
624
|
+
|
|
625
|
+
if isinstance(event, dict):
|
|
626
|
+
if "message" in event:
|
|
627
|
+
event = event["message"]
|
|
628
|
+
events.append(event)
|
|
629
|
+
if "Records" in event:
|
|
630
|
+
event = event["Records"][0]
|
|
631
|
+
events.append(event)
|
|
632
|
+
if "body" in event:
|
|
633
|
+
body = event["body"]
|
|
634
|
+
if isinstance(body, str):
|
|
635
|
+
body = JsonConversions.string_to_json_obj(body)
|
|
636
|
+
if isinstance(body, dict):
|
|
637
|
+
events.append(body)
|
|
638
|
+
if "requestContext" in body:
|
|
639
|
+
events.append(body["requestContext"])
|
|
640
|
+
|
|
641
|
+
value = None
|
|
642
|
+
for e in events:
|
|
643
|
+
if container in e:
|
|
644
|
+
if container:
|
|
645
|
+
item = e[container]
|
|
646
|
+
if item is not None and key in item:
|
|
647
|
+
value = item[key]
|
|
648
|
+
|
|
649
|
+
logger.debug(
|
|
650
|
+
{
|
|
651
|
+
"action": "search_payload_for_container_element",
|
|
652
|
+
"event": event,
|
|
653
|
+
"container": container,
|
|
654
|
+
"key": key,
|
|
655
|
+
"value": value,
|
|
656
|
+
"default": default,
|
|
657
|
+
"metric_filter": "search_payload_for_container_element",
|
|
658
|
+
}
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
if value is None:
|
|
662
|
+
value = default
|
|
663
|
+
|
|
664
|
+
return value
|
|
665
|
+
|
|
666
|
+
@staticmethod
|
|
667
|
+
def get_value_from_event_body(event: dict, key: str) -> str | dict | None:
|
|
668
|
+
"""Gets the value from the body section of the event"""
|
|
669
|
+
body = LambdaEventUtility.get_body_from_event(event)
|
|
670
|
+
if body is not None and key in body:
|
|
671
|
+
return body[key]
|
|
672
|
+
|
|
673
|
+
logger.debug(
|
|
674
|
+
{
|
|
675
|
+
"info": "Could not find key in event",
|
|
676
|
+
"key": key,
|
|
677
|
+
"found": "False",
|
|
678
|
+
"event": event,
|
|
679
|
+
}
|
|
680
|
+
)
|
|
681
|
+
return None
|
|
682
|
+
|
|
683
|
+
@staticmethod
|
|
684
|
+
def get_body_from_event(event, raise_on_error=True) -> dict | None:
|
|
685
|
+
"""
|
|
686
|
+
Get the payload from the event. If more than one record is found,
|
|
687
|
+
the first record is returned.
|
|
688
|
+
"""
|
|
689
|
+
tmp = event
|
|
690
|
+
if "Records" in tmp:
|
|
691
|
+
tmp = tmp["Records"][0]
|
|
692
|
+
|
|
693
|
+
if "body" in tmp:
|
|
694
|
+
tmp = tmp["body"]
|
|
695
|
+
|
|
696
|
+
if isinstance(tmp, str):
|
|
697
|
+
try:
|
|
698
|
+
tmp = JsonConversions.string_to_json_obj(tmp, raise_on_error)
|
|
699
|
+
except Exception as e: # noqa: E722, pylint: disable=W0702
|
|
700
|
+
raise ValueError("Invalid json body in the payload.") from e
|
|
701
|
+
return tmp
|
|
702
|
+
|
|
703
|
+
@staticmethod
|
|
704
|
+
def update_event_info(event: dict, key: str, value: str | dict) -> dict:
|
|
705
|
+
"""
|
|
706
|
+
update the event with the key and value
|
|
707
|
+
"""
|
|
708
|
+
original = event
|
|
709
|
+
status = "processing"
|
|
710
|
+
outcome = "success"
|
|
711
|
+
logger.debug({"update_event_info": {"status": f"{status}", "event": event}})
|
|
712
|
+
body: dict | None = event
|
|
713
|
+
if "body" in event:
|
|
714
|
+
body = LambdaEventUtility.get_body_from_event(event)
|
|
715
|
+
|
|
716
|
+
if body is not None and isinstance(body, dict):
|
|
717
|
+
body[key] = value
|
|
718
|
+
else:
|
|
719
|
+
outcome = "failed / not found"
|
|
720
|
+
|
|
721
|
+
logger.debug(
|
|
722
|
+
{
|
|
723
|
+
"update_event_info": {
|
|
724
|
+
"status": f"{status}",
|
|
725
|
+
"outcome": f"{outcome}",
|
|
726
|
+
"event": body,
|
|
727
|
+
"original": original,
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
return body or original
|
|
733
|
+
|
|
734
|
+
@staticmethod
|
|
735
|
+
def to_snake_case_for_backend(event: Dict[str, Any] | None) -> Dict[str, Any]:
|
|
736
|
+
"""
|
|
737
|
+
Convert UI payloads from camelCase to snake_case for backend processing.
|
|
738
|
+
|
|
739
|
+
Args:
|
|
740
|
+
event: The event payload from the UI in camelCase format
|
|
741
|
+
|
|
742
|
+
Returns:
|
|
743
|
+
The payload converted to snake_case format
|
|
744
|
+
|
|
745
|
+
Raises:
|
|
746
|
+
ValueError: If the event is None or not a dictionary
|
|
747
|
+
"""
|
|
748
|
+
if event is None:
|
|
749
|
+
raise ValueError("Event payload cannot be None")
|
|
750
|
+
if not isinstance(event, dict):
|
|
751
|
+
raise ValueError(f"Event payload must be a dictionary, got {type(event)}")
|
|
752
|
+
if not event:
|
|
753
|
+
return {}
|
|
754
|
+
|
|
755
|
+
return JsonConversions.json_camel_to_snake(event)
|
|
756
|
+
|
|
757
|
+
@staticmethod
|
|
758
|
+
def to_camel_case_for_ui(payload: Union[List[Dict[str, Any]], Dict[str, Any], None]) -> Union[List[Dict[str, Any]], Dict[str, Any]]:
|
|
759
|
+
"""
|
|
760
|
+
Convert backend data from snake_case to camelCase for UI consumption.
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
payload: The backend data in snake_case format (dict or list of dicts)
|
|
764
|
+
|
|
765
|
+
Returns:
|
|
766
|
+
The payload converted to camelCase format, maintaining the same structure
|
|
767
|
+
|
|
768
|
+
Raises:
|
|
769
|
+
ValueError: If the payload is None
|
|
770
|
+
"""
|
|
771
|
+
if payload is None:
|
|
772
|
+
raise ValueError("Payload cannot be None")
|
|
773
|
+
if not payload:
|
|
774
|
+
return payload # Return empty dict/list as-is
|
|
775
|
+
|
|
776
|
+
return JsonConversions.json_snake_to_camel(payload)
|