Skip to content

Commit 9b694f7

Browse files
authored
fix: safely getting all the values for trigger tags (#593)
* solution2: safely getting all the values * lint * add comment back
1 parent 1677473 commit 9b694f7

File tree

2 files changed

+100
-10
lines changed

2 files changed

+100
-10
lines changed

datadog_lambda/trigger.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,14 @@ def parse_event_source(event: dict) -> _EventSource:
114114

115115
event_source = None
116116

117-
request_context = event.get("requestContext")
117+
# Get requestContext safely and ensure it's a dictionary
118+
request_context = event.get("requestContext", {})
119+
if not isinstance(request_context, dict):
120+
request_context = {}
121+
118122
if request_context and request_context.get("stage"):
119123
if "domainName" in request_context and detect_lambda_function_url_domain(
120-
request_context.get("domainName")
124+
request_context.get("domainName", "")
121125
):
122126
return _EventSource(EventTypes.LAMBDA_FUNCTION_URL)
123127
event_source = _EventSource(EventTypes.API_GATEWAY)
@@ -171,6 +175,8 @@ def parse_event_source(event: dict) -> _EventSource:
171175

172176
def detect_lambda_function_url_domain(domain: str) -> bool:
173177
# e.g. "etsn5fibjr.lambda-url.eu-south-1.amazonaws.com"
178+
if not isinstance(domain, str):
179+
return False
174180
domain_parts = domain.split(".")
175181
if len(domain_parts) < 2:
176182
return False
@@ -283,17 +289,28 @@ def extract_http_tags(event):
283289
Extracts HTTP facet tags from the triggering event
284290
"""
285291
http_tags = {}
286-
request_context = event.get("requestContext")
292+
293+
# Safely get request_context and ensure it's a dictionary
294+
request_context = event.get("requestContext", {})
295+
if not isinstance(request_context, dict):
296+
request_context = {}
297+
287298
path = event.get("path")
288299
method = event.get("httpMethod")
300+
289301
if request_context and request_context.get("stage"):
290-
if request_context.get("domainName"):
291-
http_tags["http.url"] = request_context.get("domainName")
302+
domain_name = request_context.get("domainName")
303+
if domain_name:
304+
http_tags["http.url"] = domain_name
292305

293306
path = request_context.get("path")
294307
method = request_context.get("httpMethod")
308+
295309
# Version 2.0 HTTP API Gateway
296-
apigateway_v2_http = request_context.get("http")
310+
apigateway_v2_http = request_context.get("http", {})
311+
if not isinstance(apigateway_v2_http, dict):
312+
apigateway_v2_http = {}
313+
297314
if event.get("version") == "2.0" and apigateway_v2_http:
298315
path = apigateway_v2_http.get("path")
299316
method = apigateway_v2_http.get("method")
@@ -303,15 +320,23 @@ def extract_http_tags(event):
303320
if method:
304321
http_tags["http.method"] = method
305322

306-
headers = event.get("headers")
323+
# Safely get headers
324+
headers = event.get("headers", {})
325+
if not isinstance(headers, dict):
326+
headers = {}
327+
307328
if headers and headers.get("Referer"):
308329
http_tags["http.referer"] = headers.get("Referer")
309330

310331
# Try to get `routeKey` from API GW v2; otherwise try to get `resource` from API GW v1
311332
route = event.get("routeKey") or event.get("resource")
312-
if route:
313-
# "GET /my/endpoint" = > "/my/endpoint"
314-
http_tags["http.route"] = route.split(" ")[-1]
333+
if route and isinstance(route, str):
334+
try:
335+
# "GET /my/endpoint" = > "/my/endpoint"
336+
http_tags["http.route"] = route.split(" ")[-1]
337+
except Exception:
338+
# If splitting fails, use the route as is
339+
http_tags["http.route"] = route
315340

316341
return http_tags
317342

tests/test_trigger.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,30 @@ def test_event_source_unsupported(self):
256256
self.assertEqual(event_source.to_string(), "unknown")
257257
self.assertEqual(event_source_arn, None)
258258

259+
def test_event_source_with_non_dict_request_context(self):
260+
# Test with requestContext as a string instead of a dict
261+
event = {"requestContext": "not_a_dict"}
262+
event_source = parse_event_source(event)
263+
# Should still return a valid event source (unknown in this case)
264+
self.assertEqual(event_source.to_string(), "unknown")
265+
266+
def test_event_source_with_invalid_domain_name(self):
267+
# Test with domainName that isn't a string
268+
event = {"requestContext": {"stage": "prod", "domainName": 12345}}
269+
event_source = parse_event_source(event)
270+
# Should detect as API Gateway since stage is present
271+
self.assertEqual(event_source.to_string(), "api-gateway")
272+
273+
def test_detect_lambda_function_url_domain_with_invalid_input(self):
274+
from datadog_lambda.trigger import detect_lambda_function_url_domain
275+
276+
# Test with non-string input
277+
self.assertFalse(detect_lambda_function_url_domain(None))
278+
self.assertFalse(detect_lambda_function_url_domain(12345))
279+
self.assertFalse(detect_lambda_function_url_domain({"not": "a-string"}))
280+
# Test with string that would normally cause an exception when split
281+
self.assertFalse(detect_lambda_function_url_domain(""))
282+
259283

260284
class GetTriggerTags(unittest.TestCase):
261285
def test_extract_trigger_tags_api_gateway(self):
@@ -530,6 +554,47 @@ def test_extract_trigger_tags_list_type_event(self):
530554
tags = extract_trigger_tags(event, ctx)
531555
self.assertEqual(tags, {})
532556

557+
def test_extract_http_tags_with_invalid_request_context(self):
558+
from datadog_lambda.trigger import extract_http_tags
559+
560+
# Test with requestContext as a string instead of a dict
561+
event = {"requestContext": "not_a_dict", "path": "/test", "httpMethod": "GET"}
562+
http_tags = extract_http_tags(event)
563+
# Should still extract valid tags from the event
564+
self.assertEqual(
565+
http_tags, {"http.url_details.path": "/test", "http.method": "GET"}
566+
)
567+
568+
def test_extract_http_tags_with_invalid_apigateway_http(self):
569+
from datadog_lambda.trigger import extract_http_tags
570+
571+
# Test with http in requestContext that's not a dict
572+
event = {
573+
"requestContext": {"stage": "prod", "http": "not_a_dict"},
574+
"version": "2.0",
575+
}
576+
http_tags = extract_http_tags(event)
577+
# Should not raise an exception
578+
self.assertEqual(http_tags, {})
579+
580+
def test_extract_http_tags_with_invalid_headers(self):
581+
from datadog_lambda.trigger import extract_http_tags
582+
583+
# Test with headers that's not a dict
584+
event = {"headers": "not_a_dict"}
585+
http_tags = extract_http_tags(event)
586+
# Should not raise an exception
587+
self.assertEqual(http_tags, {})
588+
589+
def test_extract_http_tags_with_invalid_route(self):
590+
from datadog_lambda.trigger import extract_http_tags
591+
592+
# Test with routeKey that would cause a split error
593+
event = {"routeKey": 12345} # Not a string
594+
http_tags = extract_http_tags(event)
595+
# Should not raise an exception
596+
self.assertEqual(http_tags, {})
597+
533598

534599
class ExtractHTTPStatusCodeTag(unittest.TestCase):
535600
def test_extract_http_status_code_tag_from_response_dict(self):

0 commit comments

Comments
 (0)