1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use chrono::Utc;
6use http::{Method, StatusCode};
7use serde_json::{json, Value};
8use sha2::{Digest, Sha256};
9use tokio::sync::Mutex as AsyncMutex;
10
11use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
12use fakecloud_persistence::SnapshotStore;
13
14use crate::runtime::ContainerRuntime;
15use crate::state::{
16 EventSourceMapping, LambdaFunction, LambdaSnapshot, LambdaState, SharedLambdaState,
17 LAMBDA_SNAPSHOT_SCHEMA_VERSION,
18};
19
20fn invalid_param(msg: impl Into<String>) -> AwsServiceError {
21 AwsServiceError::aws_error(
22 StatusCode::BAD_REQUEST,
23 "InvalidParameterValueException",
24 msg,
25 )
26}
27
28fn check_len(field: &str, v: &str, min: usize, max: usize) -> Result<(), AwsServiceError> {
29 if v.len() < min || v.len() > max {
30 return Err(invalid_param(format!(
31 "{field} length must be in [{min},{max}], got {}",
32 v.len()
33 )));
34 }
35 Ok(())
36}
37
38fn check_optional_len(
39 field: &str,
40 v: Option<&str>,
41 min: usize,
42 max: usize,
43) -> Result<(), AwsServiceError> {
44 if let Some(s) = v {
45 check_len(field, s, min, max)?;
46 }
47 Ok(())
48}
49
50fn check_optional_int_range(
51 field: &str,
52 v: Option<i64>,
53 min: i64,
54 max: i64,
55) -> Result<(), AwsServiceError> {
56 if let Some(n) = v {
57 if n < min || n > max {
58 return Err(invalid_param(format!(
59 "{field} must be in [{min},{max}], got {n}"
60 )));
61 }
62 }
63 Ok(())
64}
65
66const LAMBDA_PUBLISH_TO_VALUES: &[&str] = &["LATEST_PUBLISHED"];
67
68const LAMBDA_RUNTIMES: &[&str] = &[
74 "nodejs",
75 "nodejs4.3",
76 "nodejs4.3-edge",
77 "nodejs6.10",
78 "nodejs8.10",
79 "nodejs10.x",
80 "nodejs12.x",
81 "nodejs14.x",
82 "nodejs16.x",
83 "nodejs18.x",
84 "nodejs20.x",
85 "nodejs22.x",
86 "nodejs24.x",
87 "java8",
88 "java8.al2",
89 "java11",
90 "java17",
91 "java21",
92 "java25",
93 "python2.7",
94 "python3.6",
95 "python3.7",
96 "python3.8",
97 "python3.9",
98 "python3.10",
99 "python3.11",
100 "python3.12",
101 "python3.13",
102 "python3.14",
103 "dotnetcore1.0",
104 "dotnetcore2.0",
105 "dotnetcore2.1",
106 "dotnetcore3.1",
107 "dotnet6",
108 "dotnet8",
109 "dotnet10",
110 "go1.x",
111 "ruby2.5",
112 "ruby2.7",
113 "ruby3.2",
114 "ruby3.3",
115 "ruby3.4",
116 "provided",
117 "provided.al2",
118 "provided.al2023",
119];
120
121fn check_optional_enum(
122 field: &str,
123 v: Option<&str>,
124 allowed: &[&str],
125) -> Result<(), AwsServiceError> {
126 if let Some(s) = v {
127 if !allowed.contains(&s) {
128 return Err(invalid_param(format!(
129 "{field} must be one of the enum values, got '{s}'"
130 )));
131 }
132 }
133 Ok(())
134}
135
136fn prevalidate_lambda(action: &str, req: &AwsRequest) -> Result<(), AwsServiceError> {
137 let body: Value = serde_json::from_slice(&req.body).unwrap_or(Value::Null);
138 match action {
139 "PublishVersion" => {
140 check_optional_len("Description", body["Description"].as_str(), 0, 256)?;
141 check_optional_enum(
142 "PublishTo",
143 body["PublishTo"].as_str(),
144 LAMBDA_PUBLISH_TO_VALUES,
145 )?;
146 }
147 "UpdateFunctionCode" => {
148 check_optional_enum(
149 "PublishTo",
150 body["PublishTo"].as_str(),
151 LAMBDA_PUBLISH_TO_VALUES,
152 )?;
153 check_optional_len("S3Bucket", body["S3Bucket"].as_str(), 3, 63)?;
154 check_optional_len("S3Key", body["S3Key"].as_str(), 1, 1024)?;
155 check_optional_len("S3ObjectVersion", body["S3ObjectVersion"].as_str(), 1, 1024)?;
156 }
157 "UpdateFunctionConfiguration" => {
158 check_optional_len("Description", body["Description"].as_str(), 0, 256)?;
159 check_optional_len("Handler", body["Handler"].as_str(), 0, 128)?;
160 check_optional_int_range("MemorySize", body["MemorySize"].as_i64(), 128, 32768)?;
161 check_optional_int_range("Timeout", body["Timeout"].as_i64(), 1, i64::MAX)?;
162 check_optional_enum("Runtime", body["Runtime"].as_str(), LAMBDA_RUNTIMES)?;
163 }
164 _ => {}
165 }
166 Ok(())
167}
168
169pub(crate) fn action_takes_function_name(action: &str) -> bool {
174 matches!(
175 action,
176 "GetFunction"
177 | "DeleteFunction"
178 | "Invoke"
179 | "InvokeAsync"
180 | "InvokeWithResponseStream"
181 | "PublishVersion"
182 | "ListVersionsByFunction"
183 | "AddPermission"
184 | "RemovePermission"
185 | "GetPolicy"
186 | "GetFunctionConfiguration"
187 | "UpdateFunctionConfiguration"
188 | "UpdateFunctionCode"
189 | "GetFunctionConcurrency"
190 | "PutFunctionConcurrency"
191 | "DeleteFunctionConcurrency"
192 | "PutProvisionedConcurrencyConfig"
193 | "GetProvisionedConcurrencyConfig"
194 | "DeleteProvisionedConcurrencyConfig"
195 | "ListProvisionedConcurrencyConfigs"
196 | "PutFunctionEventInvokeConfig"
197 | "UpdateFunctionEventInvokeConfig"
198 | "GetFunctionEventInvokeConfig"
199 | "DeleteFunctionEventInvokeConfig"
200 | "ListFunctionEventInvokeConfigs"
201 | "CreateFunctionUrlConfig"
202 | "UpdateFunctionUrlConfig"
203 | "GetFunctionUrlConfig"
204 | "DeleteFunctionUrlConfig"
205 | "ListFunctionUrlConfigs"
206 | "PutFunctionCodeSigningConfig"
207 | "GetFunctionCodeSigningConfig"
208 | "DeleteFunctionCodeSigningConfig"
209 | "GetFunctionScalingConfig"
210 | "PutFunctionScalingConfig"
211 | "PutFunctionRecursionConfig"
212 | "GetFunctionRecursionConfig"
213 | "CreateAlias"
214 | "GetAlias"
215 | "ListAliases"
216 | "UpdateAlias"
217 | "DeleteAlias"
218 | "PutRuntimeManagementConfig"
219 | "GetRuntimeManagementConfig"
220 )
221}
222
223pub(crate) fn iam_action_name_for(op: &str) -> Option<&'static str> {
253 let action = match op {
254 "Invoke" => "InvokeFunction",
256 "InvokeWithResponseStream" => "InvokeFunction",
257 "GetLayerVersionByArn" => "GetLayerVersion",
258
259 "CreateFunction" => "CreateFunction",
261 "ListFunctions" => "ListFunctions",
262 "GetFunction" => "GetFunction",
263 "DeleteFunction" => "DeleteFunction",
264 "InvokeAsync" => "InvokeAsync",
265 "UpdateFunctionCode" => "UpdateFunctionCode",
266 "UpdateFunctionConfiguration" => "UpdateFunctionConfiguration",
267 "GetFunctionConfiguration" => "GetFunctionConfiguration",
268 "PublishVersion" => "PublishVersion",
269 "ListVersionsByFunction" => "ListVersionsByFunction",
270 "GetAccountSettings" => "GetAccountSettings",
271
272 "AddPermission" => "AddPermission",
274 "RemovePermission" => "RemovePermission",
275 "GetPolicy" => "GetPolicy",
276
277 "CreateAlias" => "CreateAlias",
279 "GetAlias" => "GetAlias",
280 "UpdateAlias" => "UpdateAlias",
281 "DeleteAlias" => "DeleteAlias",
282 "ListAliases" => "ListAliases",
283
284 "PutFunctionConcurrency" => "PutFunctionConcurrency",
286 "GetFunctionConcurrency" => "GetFunctionConcurrency",
287 "DeleteFunctionConcurrency" => "DeleteFunctionConcurrency",
288 "PutProvisionedConcurrencyConfig" => "PutProvisionedConcurrencyConfig",
289 "GetProvisionedConcurrencyConfig" => "GetProvisionedConcurrencyConfig",
290 "DeleteProvisionedConcurrencyConfig" => "DeleteProvisionedConcurrencyConfig",
291 "ListProvisionedConcurrencyConfigs" => "ListProvisionedConcurrencyConfigs",
292
293 "PutFunctionEventInvokeConfig" => "PutFunctionEventInvokeConfig",
295 "GetFunctionEventInvokeConfig" => "GetFunctionEventInvokeConfig",
296 "UpdateFunctionEventInvokeConfig" => "UpdateFunctionEventInvokeConfig",
297 "DeleteFunctionEventInvokeConfig" => "DeleteFunctionEventInvokeConfig",
298 "ListFunctionEventInvokeConfigs" => "ListFunctionEventInvokeConfigs",
299
300 "PutRuntimeManagementConfig" => "PutRuntimeManagementConfig",
302 "GetRuntimeManagementConfig" => "GetRuntimeManagementConfig",
303 "PutFunctionScalingConfig" => "PutFunctionScalingConfig",
304 "GetFunctionScalingConfig" => "GetFunctionScalingConfig",
305 "PutFunctionRecursionConfig" => "PutFunctionRecursionConfig",
306 "GetFunctionRecursionConfig" => "GetFunctionRecursionConfig",
307
308 "CreateFunctionUrlConfig" => "CreateFunctionUrlConfig",
310 "GetFunctionUrlConfig" => "GetFunctionUrlConfig",
311 "UpdateFunctionUrlConfig" => "UpdateFunctionUrlConfig",
312 "DeleteFunctionUrlConfig" => "DeleteFunctionUrlConfig",
313 "ListFunctionUrlConfigs" => "ListFunctionUrlConfigs",
314
315 "CreateEventSourceMapping" => "CreateEventSourceMapping",
317 "ListEventSourceMappings" => "ListEventSourceMappings",
318 "GetEventSourceMapping" => "GetEventSourceMapping",
319 "UpdateEventSourceMapping" => "UpdateEventSourceMapping",
320 "DeleteEventSourceMapping" => "DeleteEventSourceMapping",
321
322 "PublishLayerVersion" => "PublishLayerVersion",
324 "ListLayers" => "ListLayers",
325 "ListLayerVersions" => "ListLayerVersions",
326 "GetLayerVersion" => "GetLayerVersion",
327 "DeleteLayerVersion" => "DeleteLayerVersion",
328 "GetLayerVersionPolicy" => "GetLayerVersionPolicy",
329 "AddLayerVersionPermission" => "AddLayerVersionPermission",
330 "RemoveLayerVersionPermission" => "RemoveLayerVersionPermission",
331
332 "CreateCodeSigningConfig" => "CreateCodeSigningConfig",
334 "GetCodeSigningConfig" => "GetCodeSigningConfig",
335 "UpdateCodeSigningConfig" => "UpdateCodeSigningConfig",
336 "DeleteCodeSigningConfig" => "DeleteCodeSigningConfig",
337 "ListCodeSigningConfigs" => "ListCodeSigningConfigs",
338 "PutFunctionCodeSigningConfig" => "PutFunctionCodeSigningConfig",
339 "GetFunctionCodeSigningConfig" => "GetFunctionCodeSigningConfig",
340 "DeleteFunctionCodeSigningConfig" => "DeleteFunctionCodeSigningConfig",
341 "ListFunctionsByCodeSigningConfig" => "ListFunctionsByCodeSigningConfig",
342
343 "TagResource" => "TagResource",
345 "UntagResource" => "UntagResource",
346 "ListTags" => "ListTags",
347
348 "CreateCapacityProvider" => "CreateCapacityProvider",
350 "GetCapacityProvider" => "GetCapacityProvider",
351 "ListCapacityProviders" => "ListCapacityProviders",
352 "UpdateCapacityProvider" => "UpdateCapacityProvider",
353 "DeleteCapacityProvider" => "DeleteCapacityProvider",
354 "ListFunctionVersionsByCapacityProvider" => "ListFunctionVersionsByCapacityProvider",
355
356 "GetDurableExecution" => "GetDurableExecution",
361 "GetDurableExecutionHistory" => "GetDurableExecutionHistory",
362 "GetDurableExecutionState" => "GetDurableExecutionState",
363 "ListDurableExecutionsByFunction" => "ListDurableExecutionsByFunction",
364 "CheckpointDurableExecution" => "CheckpointDurableExecution",
365 "StopDurableExecution" => "StopDurableExecution",
366 "SendDurableExecutionCallbackSuccess" => "SendDurableExecutionCallbackSuccess",
367 "SendDurableExecutionCallbackFailure" => "SendDurableExecutionCallbackFailure",
368 "SendDurableExecutionCallbackHeartbeat" => "SendDurableExecutionCallbackHeartbeat",
369
370 _ => return None,
371 };
372 Some(action)
373}
374
375pub(crate) fn normalize_function_name(input: &str) -> String {
376 if input.is_empty() {
377 return String::new();
378 }
379
380 let decoded = percent_encoding::percent_decode_str(input)
385 .decode_utf8_lossy()
386 .into_owned();
387 let input = decoded.as_str();
388
389 if let Some(rest) = input.strip_prefix("arn:aws:lambda:") {
391 let parts: Vec<&str> = rest.splitn(5, ':').collect();
392 if parts.len() >= 4 && parts[2] == "function" && !parts[3].is_empty() {
394 return parts[3].to_string();
395 }
396 return input.to_string();
397 }
398
399 let parts: Vec<&str> = input.splitn(4, ':').collect();
401 if parts.len() >= 3 && parts[1] == "function" && parts[0].chars().all(|c| c.is_ascii_digit()) {
402 if !parts[2].is_empty() {
403 return parts[2].to_string();
404 }
405 return input.to_string();
406 }
407
408 if input.matches(':').count() == 1 {
414 if let Some((name, _qualifier)) = input.split_once(':') {
415 if !name.is_empty() && name.chars().all(is_function_name_char) {
416 return name.to_string();
417 }
418 }
419 }
420
421 input.to_string()
422}
423
424fn is_function_name_char(c: char) -> bool {
425 c.is_ascii_alphanumeric() || c == '-' || c == '_'
426}
427
428pub(crate) fn paginate_marker<T, F>(
438 mut items: Vec<T>,
439 marker: Option<&str>,
440 max_items: Option<usize>,
441 marker_of: F,
442) -> (Vec<T>, String)
443where
444 F: Fn(&T) -> String,
445{
446 items.sort_by_key(&marker_of);
447
448 let start = match marker {
449 Some(m) if !m.is_empty() => items
450 .iter()
451 .position(|it| marker_of(it) == m)
452 .map(|p| p + 1)
453 .unwrap_or(items.len()),
454 _ => 0,
455 };
456 if start >= items.len() {
457 return (Vec::new(), String::new());
458 }
459
460 let limit = max_items.filter(|&n| n > 0).unwrap_or(usize::MAX);
461 let end = start.saturating_add(limit).min(items.len());
462 let next_marker = if end < items.len() {
463 marker_of(&items[end - 1])
464 } else {
465 String::new()
466 };
467 let page: Vec<T> = items.drain(start..end).collect();
468 (page, next_marker)
469}
470
471pub(crate) fn marker_page_size(req: &AwsRequest) -> Option<usize> {
474 req.query_params
475 .get("MaxItems")
476 .and_then(|s| s.parse::<usize>().ok())
477}
478
479pub(crate) fn qualifier_from_function_ref(input: &str) -> Option<String> {
486 if input.is_empty() {
487 return None;
488 }
489 let decoded = percent_encoding::percent_decode_str(input)
490 .decode_utf8_lossy()
491 .into_owned();
492 let input = decoded.as_str();
493
494 if let Some(rest) = input.strip_prefix("arn:aws:lambda:") {
495 let parts: Vec<&str> = rest.splitn(5, ':').collect();
497 if parts.len() == 5 && parts[2] == "function" && !parts[4].is_empty() {
498 return Some(parts[4].to_string());
499 }
500 return None;
501 }
502 let parts: Vec<&str> = input.splitn(4, ':').collect();
504 if parts.len() == 4
505 && parts[1] == "function"
506 && parts[0].chars().all(|c| c.is_ascii_digit())
507 && !parts[3].is_empty()
508 {
509 return Some(parts[3].to_string());
510 }
511 if input.matches(':').count() == 1 {
513 if let Some((name, qualifier)) = input.split_once(':') {
514 if !name.is_empty() && name.chars().all(is_function_name_char) && !qualifier.is_empty()
515 {
516 return Some(qualifier.to_string());
517 }
518 }
519 }
520 None
521}
522
523pub(crate) fn validate_ephemeral_storage(size: i64) -> Result<i64, AwsServiceError> {
528 if !(512..=10240).contains(&size) {
529 return Err(AwsServiceError::aws_error(
530 StatusCode::BAD_REQUEST,
531 "InvalidParameterValueException",
532 format!(
533 "Value {size} at 'ephemeralStorage.size' failed to satisfy constraint: \
534 Member must satisfy constraint: [Member must have value less than or equal to 10240, \
535 Member must have value greater than or equal to 512]"
536 ),
537 ));
538 }
539 Ok(size)
540}
541
542struct CreateFunctionInput {
546 function_name: String,
547 runtime: String,
548 role: String,
549 handler: String,
550 description: String,
551 timeout: i64,
552 memory_size: i64,
553 package_type: String,
554 tags: BTreeMap<String, String>,
555 environment: BTreeMap<String, String>,
556 architectures: Vec<String>,
557 code_zip: Option<Vec<u8>>,
558 code_fallback: Vec<u8>,
559 image_uri: Option<String>,
560 layer_arns: Vec<String>,
561 tracing_mode: Option<String>,
562 kms_key_arn: Option<String>,
563 ephemeral_storage_size: Option<i64>,
564 vpc_config: Option<serde_json::Value>,
565 snap_start: Option<serde_json::Value>,
566 dead_letter_config_arn: Option<String>,
567 file_system_configs: Vec<serde_json::Value>,
568 logging_config: Option<serde_json::Value>,
569 image_config: Option<serde_json::Value>,
570 durable_config: Option<serde_json::Value>,
571}
572
573impl CreateFunctionInput {
574 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
575 let function_name = body["FunctionName"]
576 .as_str()
577 .ok_or_else(|| {
578 AwsServiceError::aws_error(
579 StatusCode::BAD_REQUEST,
580 "InvalidParameterValueException",
581 "FunctionName is required",
582 )
583 })?
584 .to_string();
585
586 let tags: BTreeMap<String, String> = body["Tags"]
587 .as_object()
588 .map(|m| {
589 m.iter()
590 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
591 .collect()
592 })
593 .unwrap_or_default();
594
595 let environment: BTreeMap<String, String> = body["Environment"]["Variables"]
596 .as_object()
597 .map(|m| {
598 m.iter()
599 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
600 .collect()
601 })
602 .unwrap_or_default();
603
604 let architectures = body["Architectures"]
605 .as_array()
606 .map(|a| {
607 a.iter()
608 .filter_map(|v| v.as_str().map(|s| s.to_string()))
609 .collect()
610 })
611 .unwrap_or_else(|| vec!["x86_64".to_string()]);
612
613 let code_zip: Option<Vec<u8>> = match body["Code"]["ZipFile"].as_str() {
614 Some(b64) => Some(
615 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64).map_err(
616 |_| {
617 AwsServiceError::aws_error(
618 StatusCode::BAD_REQUEST,
619 "InvalidParameterValueException",
620 "Could not decode Code.ZipFile: invalid base64",
621 )
622 },
623 )?,
624 ),
625 None => None,
626 };
627
628 let code_fallback = serde_json::to_vec(&body["Code"]).unwrap_or_default();
629
630 let package_type = body["PackageType"].as_str().unwrap_or("Zip").to_string();
631 let image_uri = if package_type == "Image" {
636 body["Code"]["ImageUri"].as_str().map(String::from)
637 } else {
638 None
639 };
640
641 if package_type == "Image" && image_uri.is_none() {
645 return Err(AwsServiceError::aws_error(
646 StatusCode::BAD_REQUEST,
647 "InvalidParameterValueException",
648 "Code.ImageUri is required when PackageType is Image",
649 ));
650 }
651
652 let layer_arns: Vec<String> = body["Layers"]
653 .as_array()
654 .map(|arr| {
655 arr.iter()
656 .filter_map(|v| v.as_str().map(String::from))
657 .collect()
658 })
659 .unwrap_or_default();
660
661 let tracing_mode = body["TracingConfig"]["Mode"].as_str().map(String::from);
662 let kms_key_arn = body["KMSKeyArn"].as_str().map(String::from);
663 let ephemeral_storage_size = match body["EphemeralStorage"]["Size"].as_i64() {
664 Some(size) => Some(validate_ephemeral_storage(size)?),
665 None => None,
666 };
667 let vpc_config = body["VpcConfig"]
668 .is_object()
669 .then(|| body["VpcConfig"].clone());
670 let snap_start = body["SnapStart"]
671 .is_object()
672 .then(|| body["SnapStart"].clone());
673 let dead_letter_config_arn = body["DeadLetterConfig"]["TargetArn"]
674 .as_str()
675 .map(String::from);
676 let file_system_configs = body["FileSystemConfigs"]
677 .as_array()
678 .cloned()
679 .unwrap_or_default();
680 let logging_config = body["LoggingConfig"]
681 .is_object()
682 .then(|| body["LoggingConfig"].clone());
683 let image_config = body["ImageConfig"]
684 .is_object()
685 .then(|| body["ImageConfig"].clone());
686 let durable_config = body["DurableConfig"]
687 .is_object()
688 .then(|| body["DurableConfig"].clone());
689
690 Ok(Self {
691 function_name,
692 runtime: body["Runtime"].as_str().unwrap_or("python3.12").to_string(),
693 role: body["Role"].as_str().unwrap_or("").to_string(),
694 handler: body["Handler"]
695 .as_str()
696 .unwrap_or("index.handler")
697 .to_string(),
698 description: body["Description"].as_str().unwrap_or("").to_string(),
699 timeout: body["Timeout"].as_i64().unwrap_or(3),
700 memory_size: body["MemorySize"].as_i64().unwrap_or(128),
701 package_type,
702 tags,
703 environment,
704 architectures,
705 code_zip,
706 code_fallback,
707 image_uri,
708 layer_arns,
709 tracing_mode,
710 kms_key_arn,
711 ephemeral_storage_size,
712 vpc_config,
713 snap_start,
714 dead_letter_config_arn,
715 file_system_configs,
716 logging_config,
717 image_config,
718 durable_config,
719 })
720 }
721}
722
723#[derive(Debug, Clone, Copy, PartialEq, Eq)]
725pub enum InvocationType {
726 RequestResponse,
727 Event,
728 DryRun,
729}
730
731impl InvocationType {
732 pub fn from_header(value: Option<&str>) -> Self {
733 match value {
734 Some("Event") => Self::Event,
735 Some("DryRun") => Self::DryRun,
736 _ => Self::RequestResponse,
737 }
738 }
739}
740
741fn route_to_destination(
745 bus: Arc<fakecloud_core::delivery::DeliveryBus>,
746 function_arn: &str,
747 request_payload: &[u8],
748 result: &Result<Vec<u8>, String>,
749 destination_config: Option<&serde_json::Value>,
750) {
751 let Some(cfg) = destination_config else {
752 return;
753 };
754 let (key, condition, response_value): (&str, &str, serde_json::Value) = match result {
755 Ok(bytes) => (
756 "OnSuccess",
757 "Success",
758 serde_json::from_slice(bytes).unwrap_or(serde_json::Value::Null),
759 ),
760 Err(err) => (
761 "OnFailure",
762 "RetriesExhausted",
763 serde_json::json!({ "errorMessage": err }),
764 ),
765 };
766 let Some(dest) = cfg
767 .get(key)
768 .and_then(|v| v.get("Destination"))
769 .and_then(|v| v.as_str())
770 else {
771 return;
772 };
773 let request_payload_v: serde_json::Value =
774 serde_json::from_slice(request_payload).unwrap_or(serde_json::Value::Null);
775 let record = serde_json::json!({
776 "version": "1.0",
777 "timestamp": chrono::Utc::now().to_rfc3339(),
778 "requestContext": {
779 "requestId": uuid::Uuid::new_v4().to_string(),
780 "functionArn": format!("{function_arn}:$LATEST"),
781 "condition": condition,
782 "approximateInvokeCount": 1,
783 },
784 "requestPayload": request_payload_v,
785 "responseContext": {
786 "statusCode": 200,
787 "executedVersion": "$LATEST",
788 },
789 "responsePayload": response_value,
790 });
791 let body = record.to_string();
792 if dest.contains(":sqs:") {
793 bus.send_to_sqs(dest, &body, &std::collections::HashMap::new());
794 } else if dest.contains(":sns:") {
795 bus.publish_to_sns(dest, &body, None);
796 } else if dest.contains(":lambda:") {
797 let dest = dest.to_string();
798 let payload = body.clone();
799 tokio::spawn(async move {
800 let _ = bus.invoke_lambda(&dest, &payload).await;
801 });
802 } else if dest.contains(":events:") || dest.contains(":eventbridge:") {
803 let detail_type = if result.is_ok() {
804 "Lambda Function Invocation Result - Success"
805 } else {
806 "Lambda Function Invocation Result - Failure"
807 };
808 bus.put_event_to_eventbridge("lambda", detail_type, &body, "default");
809 }
810}
811
812pub(crate) struct ConcurrencyGuard {
818 pub(crate) map: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
819 pub(crate) key: String,
820}
821
822impl Drop for ConcurrencyGuard {
823 fn drop(&mut self) {
824 let mut m = self.map.write();
825 let n = m.get(&self.key).copied().unwrap_or(0);
826 if n <= 1 {
827 m.remove(&self.key);
828 } else {
829 m.insert(self.key.clone(), n - 1);
830 }
831 }
832}
833
834fn function_config_unchanged_for_publish(
850 prev: &LambdaFunction,
851 live: &LambdaFunction,
852 effective_description: &str,
853) -> bool {
854 prev.code_sha256 == live.code_sha256
855 && prev.code_size == live.code_size
856 && prev.image_uri == live.image_uri
857 && prev.package_type == live.package_type
858 && prev.runtime == live.runtime
859 && prev.role == live.role
860 && prev.handler == live.handler
861 && prev.description == effective_description
862 && prev.timeout == live.timeout
863 && prev.memory_size == live.memory_size
864 && prev.environment == live.environment
865 && prev.architectures == live.architectures
866 && prev.layers.len() == live.layers.len()
867 && prev
868 .layers
869 .iter()
870 .zip(live.layers.iter())
871 .all(|(a, b)| a.arn == b.arn && a.code_size == b.code_size)
872 && prev.tracing_mode == live.tracing_mode
873 && prev.kms_key_arn == live.kms_key_arn
874 && prev.ephemeral_storage_size == live.ephemeral_storage_size
875 && prev.vpc_config == live.vpc_config
876 && prev.dead_letter_config_arn == live.dead_letter_config_arn
877 && prev.file_system_configs == live.file_system_configs
878 && prev.logging_config == live.logging_config
879 && prev.image_config == live.image_config
880 && prev.signing_profile_version_arn == live.signing_profile_version_arn
881 && prev.signing_job_arn == live.signing_job_arn
882 && prev.runtime_version_config == live.runtime_version_config
883 && snap_start_apply_on_eq(prev.snap_start.as_ref(), live.snap_start.as_ref())
884}
885
886fn snap_start_apply_on_eq(prev: Option<&Value>, live: Option<&Value>) -> bool {
895 let prev_apply = prev
896 .and_then(|v| v.get("ApplyOn"))
897 .and_then(|v| v.as_str())
898 .unwrap_or("None");
899 let live_apply = live
900 .and_then(|v| v.get("ApplyOn"))
901 .and_then(|v| v.as_str())
902 .unwrap_or("None");
903 prev_apply == live_apply
904}
905
906pub(crate) fn resolve_qualifier_to_version(
909 state: &LambdaState,
910 function_name: &str,
911 qualifier: Option<&str>,
912) -> Option<String> {
913 let q = qualifier?;
914 if q == "$LATEST" {
915 return None;
916 }
917 if q.chars().all(|c| c.is_ascii_digit()) {
918 return Some(q.to_string());
919 }
920 let alias_key = format!("{function_name}:{q}");
921 let alias = state.aliases.get(&alias_key)?;
922 let primary = alias.function_version.clone();
923 let routing = alias
924 .routing_config
925 .as_ref()
926 .and_then(|rc| rc.get("AdditionalVersionWeights"))
927 .and_then(|m| m.as_object());
928 let Some(weights) = routing else {
929 return Some(primary);
930 };
931 let mut additional: Vec<(String, f64)> = Vec::with_capacity(weights.len());
934 let mut sum: f64 = 0.0;
935 for (ver, w) in weights {
936 let weight = w.as_f64().unwrap_or(0.0).clamp(0.0, 1.0);
937 sum += weight;
938 additional.push((ver.clone(), weight));
939 }
940 let primary_weight = (1.0 - sum).max(0.0);
941 let pick: f64 = {
942 use std::cell::Cell;
947 thread_local! {
948 static RNG: Cell<u64> = const { Cell::new(0x9E37_79B9_7F4A_7C15) };
949 }
950 let now_nanos = std::time::SystemTime::now()
951 .duration_since(std::time::UNIX_EPOCH)
952 .map(|d| d.as_nanos() as u64)
953 .unwrap_or(0);
954 RNG.with(|cell| {
955 let mut s = cell.get() ^ now_nanos;
956 s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
958 let mut z = s;
959 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
960 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
961 z ^= z >> 31;
962 cell.set(s);
963 (z >> 11) as f64 / ((1u64 << 53) as f64)
964 })
965 };
966 let mut acc = primary_weight;
967 if pick < acc {
968 return Some(primary);
969 }
970 for (ver, w) in &additional {
971 acc += w;
972 if pick < acc {
973 return Some(ver.clone());
974 }
975 }
976 Some(primary)
977}
978
979pub struct LambdaService {
980 pub(crate) state: SharedLambdaState,
981 pub(crate) runtime: Option<Arc<ContainerRuntime>>,
982 snapshot_store: Option<Arc<dyn SnapshotStore>>,
983 snapshot_lock: Arc<AsyncMutex<()>>,
984 pub(crate) delivery_bus: Option<Arc<fakecloud_core::delivery::DeliveryBus>>,
985 pub(crate) role_trust_validator: Option<Arc<dyn fakecloud_core::auth::RoleTrustValidator>>,
986 pub(crate) s3_delivery: Option<Arc<dyn fakecloud_core::delivery::S3Delivery>>,
987 pub(crate) inflight_invocations: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
994}
995
996mod functions;
997mod init;
998mod invoke;
999mod publish;
1000
1001impl LambdaService {
1002 pub fn new(state: SharedLambdaState) -> Self {
1003 Self {
1004 state,
1005 runtime: None,
1006 snapshot_store: None,
1007 snapshot_lock: Arc::new(AsyncMutex::new(())),
1008 delivery_bus: None,
1009 role_trust_validator: None,
1010 s3_delivery: None,
1011 inflight_invocations: Arc::new(parking_lot::RwLock::new(BTreeMap::new())),
1012 }
1013 }
1014
1015 pub fn with_s3_delivery(mut self, s3: Arc<dyn fakecloud_core::delivery::S3Delivery>) -> Self {
1016 self.s3_delivery = Some(s3);
1017 self
1018 }
1019
1020 pub fn with_runtime(mut self, runtime: Arc<ContainerRuntime>) -> Self {
1021 self.runtime = Some(runtime);
1022 self
1023 }
1024
1025 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
1026 self.snapshot_store = Some(store);
1027 self
1028 }
1029
1030 pub fn with_delivery_bus(mut self, bus: Arc<fakecloud_core::delivery::DeliveryBus>) -> Self {
1031 self.delivery_bus = Some(bus);
1032 self
1033 }
1034
1035 pub fn with_role_trust_validator(
1036 mut self,
1037 validator: Arc<dyn fakecloud_core::auth::RoleTrustValidator>,
1038 ) -> Self {
1039 self.role_trust_validator = Some(validator);
1040 self
1041 }
1042
1043 async fn save_snapshot(&self) {
1044 save_lambda_snapshot(
1045 &self.state,
1046 self.snapshot_store.clone(),
1047 &self.snapshot_lock,
1048 )
1049 .await;
1050 }
1051
1052 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
1057 let store = self.snapshot_store.clone()?;
1058 let state = self.state.clone();
1059 let lock = self.snapshot_lock.clone();
1060 Some(Arc::new(move || {
1061 let state = state.clone();
1062 let store = store.clone();
1063 let lock = lock.clone();
1064 Box::pin(async move {
1065 save_lambda_snapshot(&state, Some(store), &lock).await;
1066 })
1067 }))
1068 }
1069}
1070
1071pub async fn save_lambda_snapshot(
1077 state: &SharedLambdaState,
1078 store: Option<Arc<dyn SnapshotStore>>,
1079 lock: &AsyncMutex<()>,
1080) {
1081 let Some(store) = store else {
1082 return;
1083 };
1084 let _guard = lock.lock().await;
1085 let snapshot = LambdaSnapshot {
1086 schema_version: LAMBDA_SNAPSHOT_SCHEMA_VERSION,
1087 accounts: Some(state.read().clone()),
1088 state: None,
1089 };
1090 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
1091 let bytes = serde_json::to_vec(&snapshot)
1092 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
1093 store.save(&bytes)
1094 })
1095 .await;
1096 match join {
1097 Ok(Ok(())) => {}
1098 Ok(Err(err)) => tracing::error!(%err, "failed to write lambda snapshot"),
1099 Err(err) => tracing::error!(%err, "lambda snapshot task panicked"),
1100 }
1101}
1102
1103#[async_trait]
1104impl AwsService for LambdaService {
1105 fn service_name(&self) -> &str {
1106 "lambda"
1107 }
1108
1109 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1110 let (action, resource_name) = Self::resolve_action(&req).ok_or_else(|| {
1111 const KNOWN_COLLECTIONS: &[&str] = &[
1122 "functions",
1123 "layers",
1124 "layers-by-arn",
1125 "event-source-mappings",
1126 "tags",
1127 "account-settings",
1128 "code-signing-configs",
1129 ];
1130 let is_known_collection = req
1131 .path_segments
1132 .get(1)
1133 .map(|s| KNOWN_COLLECTIONS.contains(&s.as_str()))
1134 .unwrap_or(false);
1135 if is_known_collection {
1136 AwsServiceError::aws_error(
1137 StatusCode::BAD_REQUEST,
1138 "InvalidParameterValueException",
1139 format!(
1140 "Could not route request {} {} — missing or invalid identifier",
1141 req.method, req.raw_path
1142 ),
1143 )
1144 } else {
1145 AwsServiceError::aws_error(
1146 StatusCode::NOT_FOUND,
1147 "UnknownOperationException",
1148 format!("Unknown operation: {} {}", req.method, req.raw_path),
1149 )
1150 }
1151 })?;
1152
1153 let arn_embedded_qualifier = resource_name
1161 .as_deref()
1162 .and_then(qualifier_from_function_ref);
1163 let resource_name = if action_takes_function_name(action) {
1164 if let Some(raw) = resource_name.as_ref() {
1171 let decoded = crate::extras::percent_decode_for_length(raw);
1176 let len = decoded.chars().count();
1177 let limit = if decoded.starts_with("arn:") {
1188 200
1189 } else {
1190 140
1191 };
1192 if decoded.is_empty() || len > limit {
1193 let (code, msg) = if action == "InvokeAsync" {
1194 (
1195 "ResourceNotFoundException",
1196 format!("Function not found: {}", raw),
1197 )
1198 } else {
1199 (
1200 "InvalidParameterValueException",
1201 format!(
1202 "1 validation error detected: Value '{}' at 'functionName' failed to \
1203 satisfy constraint: Member must have length less than or equal to 140",
1204 raw
1205 ),
1206 )
1207 };
1208 return Err(AwsServiceError::aws_error(
1209 if action == "InvokeAsync" {
1210 StatusCode::NOT_FOUND
1211 } else {
1212 StatusCode::BAD_REQUEST
1213 },
1214 code,
1215 msg,
1216 ));
1217 }
1218 }
1219 resource_name.map(|s| normalize_function_name(&s))
1220 } else {
1221 resource_name
1222 };
1223
1224 if let Some(raw) = req.query_params.get("MaxItems") {
1230 let n = raw.parse::<i64>().map_err(|_| {
1234 AwsServiceError::aws_error(
1235 StatusCode::BAD_REQUEST,
1236 "InvalidParameterValueException",
1237 format!("MaxItems must be a number (got '{raw}')"),
1238 )
1239 })?;
1240 let max = match action {
1241 "ListLayers"
1242 | "ListLayerVersions"
1243 | "ListFunctionUrlConfigs"
1244 | "ListProvisionedConcurrencyConfigs"
1245 | "ListFunctionEventInvokeConfigs"
1246 | "ListAliases" => 50,
1247 _ => 10000,
1248 };
1249 if !(1..=max).contains(&n) {
1250 return Err(AwsServiceError::aws_error(
1251 StatusCode::BAD_REQUEST,
1252 "InvalidParameterValueException",
1253 format!("MaxItems must be between 1 and {} (got {})", max, n),
1254 ));
1255 }
1256 }
1257
1258 if let Some(q) = req.query_params.get("Qualifier") {
1263 let len = q.chars().count();
1264 if q.is_empty() || len > 128 {
1265 return Err(AwsServiceError::aws_error(
1266 StatusCode::BAD_REQUEST,
1267 "InvalidParameterValueException",
1268 format!("Qualifier must be 1..128 characters (got length {})", len),
1269 ));
1270 }
1271 }
1272 if let Some(fv) = req.query_params.get("FunctionVersion") {
1275 let len = fv.chars().count();
1276 if fv.is_empty() || len > 1024 {
1277 return Err(AwsServiceError::aws_error(
1278 StatusCode::BAD_REQUEST,
1279 "InvalidParameterValueException",
1280 format!(
1281 "FunctionVersion must be 1..1024 characters (got length {})",
1282 len
1283 ),
1284 ));
1285 }
1286 }
1287
1288 let mutates = matches!(
1289 action,
1290 "CreateFunction"
1291 | "DeleteFunction"
1292 | "PublishVersion"
1293 | "AddPermission"
1294 | "RemovePermission"
1295 | "CreateEventSourceMapping"
1296 | "DeleteEventSourceMapping"
1297 | "UpdateEventSourceMapping"
1298 | "UpdateFunctionCode"
1299 | "UpdateFunctionConfiguration"
1300 | "CreateAlias"
1301 | "DeleteAlias"
1302 | "UpdateAlias"
1303 | "PublishLayerVersion"
1304 | "DeleteLayerVersion"
1305 | "AddLayerVersionPermission"
1306 | "RemoveLayerVersionPermission"
1307 | "CreateFunctionUrlConfig"
1308 | "DeleteFunctionUrlConfig"
1309 | "UpdateFunctionUrlConfig"
1310 | "PutFunctionConcurrency"
1311 | "DeleteFunctionConcurrency"
1312 | "PutProvisionedConcurrencyConfig"
1313 | "DeleteProvisionedConcurrencyConfig"
1314 | "CreateCodeSigningConfig"
1315 | "UpdateCodeSigningConfig"
1316 | "DeleteCodeSigningConfig"
1317 | "PutFunctionCodeSigningConfig"
1318 | "DeleteFunctionCodeSigningConfig"
1319 | "PutFunctionEventInvokeConfig"
1320 | "UpdateFunctionEventInvokeConfig"
1321 | "DeleteFunctionEventInvokeConfig"
1322 | "PutRuntimeManagementConfig"
1323 | "PutFunctionScalingConfig"
1324 | "PutFunctionRecursionConfig"
1325 | "TagResource"
1326 | "UntagResource"
1327 | "InvokeAsync"
1328 | "InvokeWithResponseStream"
1329 );
1330
1331 let aid = &req.account_id;
1332 prevalidate_lambda(action, &req)?;
1337 let result = match action {
1338 "CreateFunction" => self.create_function(&req),
1339 "ListFunctions" => self.list_functions(
1340 aid,
1341 req.query_params.get("FunctionVersion").map(String::as_str),
1342 req.query_params.get("Marker").map(String::as_str),
1343 marker_page_size(&req),
1344 ),
1345 "GetFunction" => self.get_function(
1346 &req,
1347 resource_name.as_deref().unwrap_or(""),
1348 aid,
1349 req.region.as_str(),
1350 req.query_params.get("Qualifier").map(String::as_str),
1351 ),
1352 "DeleteFunction" => self.delete_function(
1353 resource_name.as_deref().unwrap_or(""),
1354 aid,
1355 req.query_params.get("Qualifier").map(String::as_str),
1356 ),
1357 "Invoke" => {
1358 let invocation_type = InvocationType::from_header(
1359 req.headers
1360 .get("x-amz-invocation-type")
1361 .and_then(|v| v.to_str().ok()),
1362 );
1363 let log_tail = req
1364 .headers
1365 .get("x-amz-log-type")
1366 .and_then(|v| v.to_str().ok())
1367 == Some("Tail");
1368 let qualifier = req
1371 .query_params
1372 .get("Qualifier")
1373 .map(String::as_str)
1374 .or(arn_embedded_qualifier.as_deref());
1375 self.invoke(
1376 resource_name.as_deref().unwrap_or(""),
1377 &req.body,
1378 aid,
1379 invocation_type,
1380 qualifier,
1381 log_tail,
1382 )
1383 .await
1384 }
1385 "InvokeAsync" => {
1386 let name = resource_name.as_deref().unwrap_or("");
1392 let accounts = self.state.read();
1393 let exists = accounts
1394 .get(aid)
1395 .map(|s| s.functions.contains_key(name))
1396 .unwrap_or(false);
1397 if !exists {
1398 Err(AwsServiceError::aws_error(
1399 StatusCode::NOT_FOUND,
1400 "ResourceNotFoundException",
1401 format!("Function not found: {}", name),
1402 ))
1403 } else {
1404 Ok(AwsResponse::json(
1405 StatusCode::ACCEPTED,
1406 json!({ "Status": 202 }).to_string(),
1407 ))
1408 }
1409 }
1410 "PublishVersion" => {
1411 self.publish_version(resource_name.as_deref().unwrap_or(""), aid, &req)
1412 }
1413 "AddPermission" => self.add_permission(resource_name.as_deref().unwrap_or(""), &req),
1414 "GetPolicy" => self.get_policy(
1415 resource_name.as_deref().unwrap_or(""),
1416 aid,
1417 req.query_params.get("Qualifier").map(String::as_str),
1418 ),
1419 "RemovePermission" => {
1420 let sid = req.path_segments.get(4).cloned().unwrap_or_default();
1422 self.remove_permission(
1423 resource_name.as_deref().unwrap_or(""),
1424 &sid,
1425 aid,
1426 req.query_params.get("Qualifier").map(String::as_str),
1427 )
1428 }
1429 "CreateEventSourceMapping" => self.create_event_source_mapping(&req),
1430 "ListEventSourceMappings" => {
1431 if let Some(fn_name) = req.query_params.get("FunctionName") {
1435 let len = fn_name.chars().count();
1436 if fn_name.is_empty() || len > 140 {
1437 return Err(AwsServiceError::aws_error(
1438 StatusCode::BAD_REQUEST,
1439 "InvalidParameterValueException",
1440 "FunctionName must be 1..140 characters",
1441 ));
1442 }
1443 }
1444 self.list_event_source_mappings(aid, &req)
1445 }
1446 "GetEventSourceMapping" => {
1447 self.get_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1448 }
1449 "DeleteEventSourceMapping" => {
1450 self.delete_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1451 }
1452 "CreateCapacityProvider" => {
1453 crate::workflows::create_capacity_provider(&self.state, &req, &req.json_body())
1454 }
1455 "GetCapacityProvider" => crate::workflows::get_capacity_provider(
1456 &self.state,
1457 &req,
1458 resource_name.as_deref().unwrap_or(""),
1459 ),
1460 "ListCapacityProviders" => crate::workflows::list_capacity_providers(&self.state, &req),
1461 "UpdateCapacityProvider" => crate::workflows::update_capacity_provider(
1462 &self.state,
1463 &req,
1464 resource_name.as_deref().unwrap_or(""),
1465 &req.json_body(),
1466 ),
1467 "DeleteCapacityProvider" => crate::workflows::delete_capacity_provider(
1468 &self.state,
1469 &req,
1470 resource_name.as_deref().unwrap_or(""),
1471 ),
1472 "ListFunctionVersionsByCapacityProvider" => {
1473 crate::workflows::list_function_versions_by_capacity_provider(
1474 &self.state,
1475 &req,
1476 resource_name.as_deref().unwrap_or(""),
1477 )
1478 }
1479 "GetDurableExecution" => crate::workflows::get_durable_execution(
1480 &self.state,
1481 &req,
1482 resource_name.as_deref().unwrap_or(""),
1483 ),
1484 "GetDurableExecutionHistory" => crate::workflows::get_durable_execution_history(
1485 &self.state,
1486 &req,
1487 resource_name.as_deref().unwrap_or(""),
1488 ),
1489 "GetDurableExecutionState" => crate::workflows::get_durable_execution_state(
1490 &self.state,
1491 &req,
1492 resource_name.as_deref().unwrap_or(""),
1493 ),
1494 "ListDurableExecutionsByFunction" => {
1495 crate::workflows::list_durable_executions_by_function(
1496 &self.state,
1497 &req,
1498 resource_name.as_deref().unwrap_or(""),
1499 )
1500 }
1501 "CheckpointDurableExecution" => crate::workflows::checkpoint_durable_execution(
1502 &self.state,
1503 &req,
1504 resource_name.as_deref().unwrap_or(""),
1505 &req.json_body(),
1506 ),
1507 "StopDurableExecution" => crate::workflows::stop_durable_execution(
1508 &self.state,
1509 &req,
1510 resource_name.as_deref().unwrap_or(""),
1511 ),
1512 "SendDurableExecutionCallbackSuccess" => crate::workflows::send_callback_success(
1513 &self.state,
1514 &req,
1515 resource_name.as_deref().unwrap_or(""),
1516 ),
1517 "SendDurableExecutionCallbackFailure" => crate::workflows::send_callback_failure(
1518 &self.state,
1519 &req,
1520 resource_name.as_deref().unwrap_or(""),
1521 ),
1522 "SendDurableExecutionCallbackHeartbeat" => crate::workflows::send_callback_heartbeat(
1523 &self.state,
1524 &req,
1525 resource_name.as_deref().unwrap_or(""),
1526 ),
1527 other => {
1528 self.handle_extra(other, resource_name.as_deref(), &req)
1529 .await
1530 }
1531 };
1532 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
1533 self.save_snapshot().await;
1534 }
1535 result
1536 }
1537
1538 fn supported_actions(&self) -> &[&str] {
1539 &[
1540 "CreateFunction",
1541 "GetFunction",
1542 "DeleteFunction",
1543 "ListFunctions",
1544 "Invoke",
1545 "InvokeAsync",
1546 "InvokeWithResponseStream",
1547 "PublishVersion",
1548 "ListVersionsByFunction",
1549 "AddPermission",
1550 "RemovePermission",
1551 "GetPolicy",
1552 "CreateEventSourceMapping",
1553 "ListEventSourceMappings",
1554 "GetEventSourceMapping",
1555 "UpdateEventSourceMapping",
1556 "DeleteEventSourceMapping",
1557 "GetFunctionConfiguration",
1558 "UpdateFunctionConfiguration",
1559 "UpdateFunctionCode",
1560 "GetAccountSettings",
1561 "CreateAlias",
1562 "GetAlias",
1563 "ListAliases",
1564 "UpdateAlias",
1565 "DeleteAlias",
1566 "PublishLayerVersion",
1567 "GetLayerVersion",
1568 "GetLayerVersionByArn",
1569 "DeleteLayerVersion",
1570 "ListLayerVersions",
1571 "ListLayers",
1572 "GetLayerVersionPolicy",
1573 "AddLayerVersionPermission",
1574 "RemoveLayerVersionPermission",
1575 "CreateFunctionUrlConfig",
1576 "GetFunctionUrlConfig",
1577 "UpdateFunctionUrlConfig",
1578 "DeleteFunctionUrlConfig",
1579 "ListFunctionUrlConfigs",
1580 "PutFunctionConcurrency",
1581 "GetFunctionConcurrency",
1582 "DeleteFunctionConcurrency",
1583 "PutProvisionedConcurrencyConfig",
1584 "GetProvisionedConcurrencyConfig",
1585 "DeleteProvisionedConcurrencyConfig",
1586 "ListProvisionedConcurrencyConfigs",
1587 "CreateCodeSigningConfig",
1588 "GetCodeSigningConfig",
1589 "UpdateCodeSigningConfig",
1590 "DeleteCodeSigningConfig",
1591 "ListCodeSigningConfigs",
1592 "PutFunctionCodeSigningConfig",
1593 "GetFunctionCodeSigningConfig",
1594 "DeleteFunctionCodeSigningConfig",
1595 "ListFunctionsByCodeSigningConfig",
1596 "PutFunctionEventInvokeConfig",
1597 "GetFunctionEventInvokeConfig",
1598 "UpdateFunctionEventInvokeConfig",
1599 "DeleteFunctionEventInvokeConfig",
1600 "ListFunctionEventInvokeConfigs",
1601 "PutRuntimeManagementConfig",
1602 "GetRuntimeManagementConfig",
1603 "PutFunctionScalingConfig",
1604 "GetFunctionScalingConfig",
1605 "PutFunctionRecursionConfig",
1606 "GetFunctionRecursionConfig",
1607 "TagResource",
1608 "UntagResource",
1609 "ListTags",
1610 "CreateCapacityProvider",
1611 "GetCapacityProvider",
1612 "ListCapacityProviders",
1613 "UpdateCapacityProvider",
1614 "DeleteCapacityProvider",
1615 "ListFunctionVersionsByCapacityProvider",
1616 "GetDurableExecution",
1617 "GetDurableExecutionHistory",
1618 "GetDurableExecutionState",
1619 "ListDurableExecutionsByFunction",
1620 "CheckpointDurableExecution",
1621 "StopDurableExecution",
1622 "SendDurableExecutionCallbackSuccess",
1623 "SendDurableExecutionCallbackFailure",
1624 "SendDurableExecutionCallbackHeartbeat",
1625 ]
1626 }
1627
1628 fn iam_enforceable(&self) -> bool {
1629 true
1630 }
1631
1632 fn iam_action_for(&self, request: &AwsRequest) -> Option<fakecloud_core::auth::IamAction> {
1636 let (action_str, resource_name) = Self::resolve_action(request)?;
1641 let action = iam_action_name_for(action_str)?;
1649 let accounts = self.state.read();
1650 let empty = LambdaState::new(&request.account_id, &request.region);
1651 let state = accounts.get(&request.account_id).unwrap_or(&empty);
1652 let resource = match action_str {
1653 "GetFunction"
1658 | "DeleteFunction"
1659 | "Invoke"
1660 | "InvokeAsync"
1661 | "InvokeWithResponseStream"
1662 | "PublishVersion"
1663 | "ListVersionsByFunction"
1664 | "AddPermission"
1665 | "RemovePermission"
1666 | "GetPolicy"
1667 | "GetFunctionConfiguration"
1668 | "UpdateFunctionConfiguration"
1669 | "UpdateFunctionCode"
1670 | "CreateAlias"
1671 | "GetAlias"
1672 | "UpdateAlias"
1673 | "DeleteAlias"
1674 | "ListAliases"
1675 | "PutFunctionConcurrency"
1676 | "GetFunctionConcurrency"
1677 | "DeleteFunctionConcurrency"
1678 | "PutProvisionedConcurrencyConfig"
1679 | "GetProvisionedConcurrencyConfig"
1680 | "DeleteProvisionedConcurrencyConfig"
1681 | "ListProvisionedConcurrencyConfigs"
1682 | "PutFunctionEventInvokeConfig"
1683 | "GetFunctionEventInvokeConfig"
1684 | "UpdateFunctionEventInvokeConfig"
1685 | "DeleteFunctionEventInvokeConfig"
1686 | "ListFunctionEventInvokeConfigs"
1687 | "PutRuntimeManagementConfig"
1688 | "GetRuntimeManagementConfig"
1689 | "PutFunctionScalingConfig"
1690 | "GetFunctionScalingConfig"
1691 | "PutFunctionRecursionConfig"
1692 | "GetFunctionRecursionConfig"
1693 | "PutFunctionCodeSigningConfig"
1694 | "GetFunctionCodeSigningConfig"
1695 | "DeleteFunctionCodeSigningConfig"
1696 | "CreateFunctionUrlConfig"
1697 | "GetFunctionUrlConfig"
1698 | "UpdateFunctionUrlConfig"
1699 | "DeleteFunctionUrlConfig"
1700 | "ListFunctionUrlConfigs"
1701 | "ListDurableExecutionsByFunction" => {
1702 let raw = resource_name.unwrap_or_default();
1703 if raw.is_empty() {
1704 "*".to_string()
1705 } else {
1706 let name = normalize_function_name(&raw);
1712 format!(
1713 "arn:aws:lambda:{}:{}:function:{}",
1714 state.region, state.account_id, name
1715 )
1716 }
1717 }
1718 "CreateFunction" => {
1719 serde_json::from_slice::<Value>(&request.body)
1724 .ok()
1725 .and_then(|v| {
1726 v.get("FunctionName").and_then(|f| f.as_str()).map(|n| {
1727 format!(
1728 "arn:aws:lambda:{}:{}:function:{}",
1729 state.region, state.account_id, n
1730 )
1731 })
1732 })
1733 .unwrap_or_else(|| "*".to_string())
1734 }
1735 _ => "*".to_string(),
1736 };
1737 Some(fakecloud_core::auth::IamAction {
1738 service: "lambda",
1739 action,
1740 resource,
1741 })
1742 }
1743
1744 fn iam_condition_keys_for(
1745 &self,
1746 request: &AwsRequest,
1747 action: &fakecloud_core::auth::IamAction,
1748 ) -> std::collections::BTreeMap<String, Vec<String>> {
1749 let mut out = std::collections::BTreeMap::new();
1750 if action.action == "AddPermission" {
1751 if action.resource != "*" {
1752 out.insert(
1753 "lambda:functionarn".to_string(),
1754 vec![action.resource.clone()],
1755 );
1756 }
1757 if let Ok(body) = serde_json::from_slice::<Value>(&request.body) {
1758 if let Some(principal) = body.get("Principal").and_then(|p| p.as_str()) {
1759 out.insert("lambda:principal".to_string(), vec![principal.to_string()]);
1760 }
1761 }
1762 }
1763 out
1764 }
1765}
1766
1767#[path = "../service_event_sources.rs"]
1768mod service_event_sources;
1769#[path = "../service_permissions.rs"]
1770mod service_permissions;
1771
1772#[cfg(test)]
1773#[path = "../service_tests.rs"]
1774mod tests;