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 "CreateFunction" => {
140 check_optional_len("Description", body["Description"].as_str(), 0, 256)?;
141 check_optional_len("Handler", body["Handler"].as_str(), 0, 128)?;
142 check_optional_int_range("MemorySize", body["MemorySize"].as_i64(), 128, 32768)?;
143 check_optional_int_range("Timeout", body["Timeout"].as_i64(), 1, 5400)?;
144 check_optional_enum("Runtime", body["Runtime"].as_str(), LAMBDA_RUNTIMES)?;
145 }
146 "PublishVersion" => {
147 check_optional_len("Description", body["Description"].as_str(), 0, 256)?;
148 check_optional_enum(
149 "PublishTo",
150 body["PublishTo"].as_str(),
151 LAMBDA_PUBLISH_TO_VALUES,
152 )?;
153 }
154 "UpdateFunctionCode" => {
155 check_optional_enum(
156 "PublishTo",
157 body["PublishTo"].as_str(),
158 LAMBDA_PUBLISH_TO_VALUES,
159 )?;
160 check_optional_len("S3Bucket", body["S3Bucket"].as_str(), 3, 63)?;
161 check_optional_len("S3Key", body["S3Key"].as_str(), 1, 1024)?;
162 check_optional_len("S3ObjectVersion", body["S3ObjectVersion"].as_str(), 1, 1024)?;
163 }
164 "UpdateFunctionConfiguration" => {
165 check_optional_len("Description", body["Description"].as_str(), 0, 256)?;
166 check_optional_len("Handler", body["Handler"].as_str(), 0, 128)?;
167 check_optional_int_range("MemorySize", body["MemorySize"].as_i64(), 128, 32768)?;
168 check_optional_int_range("Timeout", body["Timeout"].as_i64(), 1, 5400)?;
169 check_optional_enum("Runtime", body["Runtime"].as_str(), LAMBDA_RUNTIMES)?;
170 }
171 _ => {}
172 }
173 Ok(())
174}
175
176pub(crate) fn action_takes_function_name(action: &str) -> bool {
181 matches!(
182 action,
183 "GetFunction"
184 | "DeleteFunction"
185 | "Invoke"
186 | "InvokeAsync"
187 | "InvokeWithResponseStream"
188 | "PublishVersion"
189 | "ListVersionsByFunction"
190 | "AddPermission"
191 | "RemovePermission"
192 | "GetPolicy"
193 | "GetFunctionConfiguration"
194 | "UpdateFunctionConfiguration"
195 | "UpdateFunctionCode"
196 | "GetFunctionConcurrency"
197 | "PutFunctionConcurrency"
198 | "DeleteFunctionConcurrency"
199 | "PutProvisionedConcurrencyConfig"
200 | "GetProvisionedConcurrencyConfig"
201 | "DeleteProvisionedConcurrencyConfig"
202 | "ListProvisionedConcurrencyConfigs"
203 | "PutFunctionEventInvokeConfig"
204 | "UpdateFunctionEventInvokeConfig"
205 | "GetFunctionEventInvokeConfig"
206 | "DeleteFunctionEventInvokeConfig"
207 | "ListFunctionEventInvokeConfigs"
208 | "CreateFunctionUrlConfig"
209 | "UpdateFunctionUrlConfig"
210 | "GetFunctionUrlConfig"
211 | "DeleteFunctionUrlConfig"
212 | "ListFunctionUrlConfigs"
213 | "PutFunctionCodeSigningConfig"
214 | "GetFunctionCodeSigningConfig"
215 | "DeleteFunctionCodeSigningConfig"
216 | "GetFunctionScalingConfig"
217 | "PutFunctionScalingConfig"
218 | "PutFunctionRecursionConfig"
219 | "GetFunctionRecursionConfig"
220 | "CreateAlias"
221 | "GetAlias"
222 | "ListAliases"
223 | "UpdateAlias"
224 | "DeleteAlias"
225 | "PutRuntimeManagementConfig"
226 | "GetRuntimeManagementConfig"
227 )
228}
229
230pub(crate) fn iam_action_name_for(op: &str) -> Option<&'static str> {
260 let action = match op {
261 "Invoke" => "InvokeFunction",
263 "InvokeWithResponseStream" => "InvokeFunction",
264 "GetLayerVersionByArn" => "GetLayerVersion",
265
266 "CreateFunction" => "CreateFunction",
268 "ListFunctions" => "ListFunctions",
269 "GetFunction" => "GetFunction",
270 "DeleteFunction" => "DeleteFunction",
271 "InvokeAsync" => "InvokeAsync",
272 "UpdateFunctionCode" => "UpdateFunctionCode",
273 "UpdateFunctionConfiguration" => "UpdateFunctionConfiguration",
274 "GetFunctionConfiguration" => "GetFunctionConfiguration",
275 "PublishVersion" => "PublishVersion",
276 "ListVersionsByFunction" => "ListVersionsByFunction",
277 "GetAccountSettings" => "GetAccountSettings",
278
279 "AddPermission" => "AddPermission",
281 "RemovePermission" => "RemovePermission",
282 "GetPolicy" => "GetPolicy",
283
284 "CreateAlias" => "CreateAlias",
286 "GetAlias" => "GetAlias",
287 "UpdateAlias" => "UpdateAlias",
288 "DeleteAlias" => "DeleteAlias",
289 "ListAliases" => "ListAliases",
290
291 "PutFunctionConcurrency" => "PutFunctionConcurrency",
293 "GetFunctionConcurrency" => "GetFunctionConcurrency",
294 "DeleteFunctionConcurrency" => "DeleteFunctionConcurrency",
295 "PutProvisionedConcurrencyConfig" => "PutProvisionedConcurrencyConfig",
296 "GetProvisionedConcurrencyConfig" => "GetProvisionedConcurrencyConfig",
297 "DeleteProvisionedConcurrencyConfig" => "DeleteProvisionedConcurrencyConfig",
298 "ListProvisionedConcurrencyConfigs" => "ListProvisionedConcurrencyConfigs",
299
300 "PutFunctionEventInvokeConfig" => "PutFunctionEventInvokeConfig",
302 "GetFunctionEventInvokeConfig" => "GetFunctionEventInvokeConfig",
303 "UpdateFunctionEventInvokeConfig" => "UpdateFunctionEventInvokeConfig",
304 "DeleteFunctionEventInvokeConfig" => "DeleteFunctionEventInvokeConfig",
305 "ListFunctionEventInvokeConfigs" => "ListFunctionEventInvokeConfigs",
306
307 "PutRuntimeManagementConfig" => "PutRuntimeManagementConfig",
309 "GetRuntimeManagementConfig" => "GetRuntimeManagementConfig",
310 "PutFunctionScalingConfig" => "PutFunctionScalingConfig",
311 "GetFunctionScalingConfig" => "GetFunctionScalingConfig",
312 "PutFunctionRecursionConfig" => "PutFunctionRecursionConfig",
313 "GetFunctionRecursionConfig" => "GetFunctionRecursionConfig",
314
315 "CreateFunctionUrlConfig" => "CreateFunctionUrlConfig",
317 "GetFunctionUrlConfig" => "GetFunctionUrlConfig",
318 "UpdateFunctionUrlConfig" => "UpdateFunctionUrlConfig",
319 "DeleteFunctionUrlConfig" => "DeleteFunctionUrlConfig",
320 "ListFunctionUrlConfigs" => "ListFunctionUrlConfigs",
321
322 "CreateEventSourceMapping" => "CreateEventSourceMapping",
324 "ListEventSourceMappings" => "ListEventSourceMappings",
325 "GetEventSourceMapping" => "GetEventSourceMapping",
326 "UpdateEventSourceMapping" => "UpdateEventSourceMapping",
327 "DeleteEventSourceMapping" => "DeleteEventSourceMapping",
328
329 "PublishLayerVersion" => "PublishLayerVersion",
331 "ListLayers" => "ListLayers",
332 "ListLayerVersions" => "ListLayerVersions",
333 "GetLayerVersion" => "GetLayerVersion",
334 "DeleteLayerVersion" => "DeleteLayerVersion",
335 "GetLayerVersionPolicy" => "GetLayerVersionPolicy",
336 "AddLayerVersionPermission" => "AddLayerVersionPermission",
337 "RemoveLayerVersionPermission" => "RemoveLayerVersionPermission",
338
339 "CreateCodeSigningConfig" => "CreateCodeSigningConfig",
341 "GetCodeSigningConfig" => "GetCodeSigningConfig",
342 "UpdateCodeSigningConfig" => "UpdateCodeSigningConfig",
343 "DeleteCodeSigningConfig" => "DeleteCodeSigningConfig",
344 "ListCodeSigningConfigs" => "ListCodeSigningConfigs",
345 "PutFunctionCodeSigningConfig" => "PutFunctionCodeSigningConfig",
346 "GetFunctionCodeSigningConfig" => "GetFunctionCodeSigningConfig",
347 "DeleteFunctionCodeSigningConfig" => "DeleteFunctionCodeSigningConfig",
348 "ListFunctionsByCodeSigningConfig" => "ListFunctionsByCodeSigningConfig",
349
350 "TagResource" => "TagResource",
352 "UntagResource" => "UntagResource",
353 "ListTags" => "ListTags",
354
355 "CreateCapacityProvider" => "CreateCapacityProvider",
357 "GetCapacityProvider" => "GetCapacityProvider",
358 "ListCapacityProviders" => "ListCapacityProviders",
359 "UpdateCapacityProvider" => "UpdateCapacityProvider",
360 "DeleteCapacityProvider" => "DeleteCapacityProvider",
361 "ListFunctionVersionsByCapacityProvider" => "ListFunctionVersionsByCapacityProvider",
362
363 "GetDurableExecution" => "GetDurableExecution",
368 "GetDurableExecutionHistory" => "GetDurableExecutionHistory",
369 "GetDurableExecutionState" => "GetDurableExecutionState",
370 "ListDurableExecutionsByFunction" => "ListDurableExecutionsByFunction",
371 "CheckpointDurableExecution" => "CheckpointDurableExecution",
372 "StopDurableExecution" => "StopDurableExecution",
373 "SendDurableExecutionCallbackSuccess" => "SendDurableExecutionCallbackSuccess",
374 "SendDurableExecutionCallbackFailure" => "SendDurableExecutionCallbackFailure",
375 "SendDurableExecutionCallbackHeartbeat" => "SendDurableExecutionCallbackHeartbeat",
376
377 _ => return None,
378 };
379 Some(action)
380}
381
382pub(crate) fn normalize_function_name(input: &str) -> String {
383 if input.is_empty() {
384 return String::new();
385 }
386
387 let decoded = percent_encoding::percent_decode_str(input)
392 .decode_utf8_lossy()
393 .into_owned();
394 let input = decoded.as_str();
395
396 if let Some(rest) = input.strip_prefix("arn:aws:lambda:") {
398 let parts: Vec<&str> = rest.splitn(5, ':').collect();
399 if parts.len() >= 4 && parts[2] == "function" && !parts[3].is_empty() {
401 return parts[3].to_string();
402 }
403 return input.to_string();
404 }
405
406 let parts: Vec<&str> = input.splitn(4, ':').collect();
408 if parts.len() >= 3 && parts[1] == "function" && parts[0].chars().all(|c| c.is_ascii_digit()) {
409 if !parts[2].is_empty() {
410 return parts[2].to_string();
411 }
412 return input.to_string();
413 }
414
415 if input.matches(':').count() == 1 {
421 if let Some((name, _qualifier)) = input.split_once(':') {
422 if !name.is_empty() && name.chars().all(is_function_name_char) {
423 return name.to_string();
424 }
425 }
426 }
427
428 input.to_string()
429}
430
431fn is_function_name_char(c: char) -> bool {
432 c.is_ascii_alphanumeric() || c == '-' || c == '_'
433}
434
435pub(crate) fn paginate_marker<T, F>(
445 mut items: Vec<T>,
446 marker: Option<&str>,
447 max_items: Option<usize>,
448 marker_of: F,
449) -> (Vec<T>, String)
450where
451 F: Fn(&T) -> String,
452{
453 items.sort_by_key(&marker_of);
454
455 let start = match marker {
456 Some(m) if !m.is_empty() => items
457 .iter()
458 .position(|it| marker_of(it) == m)
459 .map(|p| p + 1)
460 .unwrap_or(items.len()),
461 _ => 0,
462 };
463 if start >= items.len() {
464 return (Vec::new(), String::new());
465 }
466
467 let limit = max_items.filter(|&n| n > 0).unwrap_or(usize::MAX);
468 let end = start.saturating_add(limit).min(items.len());
469 let next_marker = if end < items.len() {
470 marker_of(&items[end - 1])
471 } else {
472 String::new()
473 };
474 let page: Vec<T> = items.drain(start..end).collect();
475 (page, next_marker)
476}
477
478pub(crate) fn marker_page_size(req: &AwsRequest) -> Option<usize> {
481 req.query_params
482 .get("MaxItems")
483 .and_then(|s| s.parse::<usize>().ok())
484}
485
486pub(crate) fn qualifier_from_function_ref(input: &str) -> Option<String> {
493 if input.is_empty() {
494 return None;
495 }
496 let decoded = percent_encoding::percent_decode_str(input)
497 .decode_utf8_lossy()
498 .into_owned();
499 let input = decoded.as_str();
500
501 if let Some(rest) = input.strip_prefix("arn:aws:lambda:") {
502 let parts: Vec<&str> = rest.splitn(5, ':').collect();
504 if parts.len() == 5 && parts[2] == "function" && !parts[4].is_empty() {
505 return Some(parts[4].to_string());
506 }
507 return None;
508 }
509 let parts: Vec<&str> = input.splitn(4, ':').collect();
511 if parts.len() == 4
512 && parts[1] == "function"
513 && parts[0].chars().all(|c| c.is_ascii_digit())
514 && !parts[3].is_empty()
515 {
516 return Some(parts[3].to_string());
517 }
518 if input.matches(':').count() == 1 {
520 if let Some((name, qualifier)) = input.split_once(':') {
521 if !name.is_empty() && name.chars().all(is_function_name_char) && !qualifier.is_empty()
522 {
523 return Some(qualifier.to_string());
524 }
525 }
526 }
527 None
528}
529
530pub(crate) fn validate_ephemeral_storage(size: i64) -> Result<i64, AwsServiceError> {
535 if !(512..=10240).contains(&size) {
536 return Err(AwsServiceError::aws_error(
537 StatusCode::BAD_REQUEST,
538 "InvalidParameterValueException",
539 format!(
540 "Value {size} at 'ephemeralStorage.size' failed to satisfy constraint: \
541 Member must satisfy constraint: [Member must have value less than or equal to 10240, \
542 Member must have value greater than or equal to 512]"
543 ),
544 ));
545 }
546 Ok(size)
547}
548
549struct CreateFunctionInput {
553 function_name: String,
554 runtime: String,
555 role: String,
556 handler: String,
557 description: String,
558 timeout: i64,
559 memory_size: i64,
560 package_type: String,
561 tags: BTreeMap<String, String>,
562 environment: BTreeMap<String, String>,
563 architectures: Vec<String>,
564 code_zip: Option<Vec<u8>>,
565 code_fallback: Vec<u8>,
566 image_uri: Option<String>,
567 layer_arns: Vec<String>,
568 tracing_mode: Option<String>,
569 kms_key_arn: Option<String>,
570 ephemeral_storage_size: Option<i64>,
571 vpc_config: Option<serde_json::Value>,
572 snap_start: Option<serde_json::Value>,
573 dead_letter_config_arn: Option<String>,
574 file_system_configs: Vec<serde_json::Value>,
575 logging_config: Option<serde_json::Value>,
576 image_config: Option<serde_json::Value>,
577 durable_config: Option<serde_json::Value>,
578}
579
580impl CreateFunctionInput {
581 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
582 let function_name = body["FunctionName"]
583 .as_str()
584 .ok_or_else(|| {
585 AwsServiceError::aws_error(
586 StatusCode::BAD_REQUEST,
587 "InvalidParameterValueException",
588 "FunctionName is required",
589 )
590 })?
591 .to_string();
592
593 let tags: BTreeMap<String, String> = body["Tags"]
594 .as_object()
595 .map(|m| {
596 m.iter()
597 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
598 .collect()
599 })
600 .unwrap_or_default();
601
602 let environment: BTreeMap<String, String> = body["Environment"]["Variables"]
603 .as_object()
604 .map(|m| {
605 m.iter()
606 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
607 .collect()
608 })
609 .unwrap_or_default();
610
611 let architectures = body["Architectures"]
612 .as_array()
613 .map(|a| {
614 a.iter()
615 .filter_map(|v| v.as_str().map(|s| s.to_string()))
616 .collect()
617 })
618 .unwrap_or_else(|| vec!["x86_64".to_string()]);
619
620 let code_zip: Option<Vec<u8>> = match body["Code"]["ZipFile"].as_str() {
621 Some(b64) => Some(
622 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64).map_err(
623 |_| {
624 AwsServiceError::aws_error(
625 StatusCode::BAD_REQUEST,
626 "InvalidParameterValueException",
627 "Could not decode Code.ZipFile: invalid base64",
628 )
629 },
630 )?,
631 ),
632 None => None,
633 };
634
635 let code_fallback = serde_json::to_vec(&body["Code"]).unwrap_or_default();
636
637 let package_type = body["PackageType"].as_str().unwrap_or("Zip").to_string();
638 let image_uri = if package_type == "Image" {
643 body["Code"]["ImageUri"].as_str().map(String::from)
644 } else {
645 None
646 };
647
648 if package_type == "Image" && image_uri.is_none() {
652 return Err(AwsServiceError::aws_error(
653 StatusCode::BAD_REQUEST,
654 "InvalidParameterValueException",
655 "Code.ImageUri is required when PackageType is Image",
656 ));
657 }
658
659 let layer_arns: Vec<String> = body["Layers"]
660 .as_array()
661 .map(|arr| {
662 arr.iter()
663 .filter_map(|v| v.as_str().map(String::from))
664 .collect()
665 })
666 .unwrap_or_default();
667
668 let tracing_mode = body["TracingConfig"]["Mode"].as_str().map(String::from);
669 let kms_key_arn = body["KMSKeyArn"].as_str().map(String::from);
670 let ephemeral_storage_size = match body["EphemeralStorage"]["Size"].as_i64() {
671 Some(size) => Some(validate_ephemeral_storage(size)?),
672 None => None,
673 };
674 let vpc_config = body["VpcConfig"]
675 .is_object()
676 .then(|| body["VpcConfig"].clone());
677 let snap_start = body["SnapStart"]
678 .is_object()
679 .then(|| body["SnapStart"].clone());
680 let dead_letter_config_arn = body["DeadLetterConfig"]["TargetArn"]
681 .as_str()
682 .map(String::from);
683 let file_system_configs = body["FileSystemConfigs"]
684 .as_array()
685 .cloned()
686 .unwrap_or_default();
687 let logging_config = body["LoggingConfig"]
688 .is_object()
689 .then(|| body["LoggingConfig"].clone());
690 let image_config = body["ImageConfig"]
691 .is_object()
692 .then(|| body["ImageConfig"].clone());
693 let durable_config = body["DurableConfig"]
694 .is_object()
695 .then(|| body["DurableConfig"].clone());
696
697 Ok(Self {
698 function_name,
699 runtime: body["Runtime"].as_str().unwrap_or("python3.12").to_string(),
700 role: body["Role"].as_str().unwrap_or("").to_string(),
701 handler: body["Handler"]
702 .as_str()
703 .unwrap_or("index.handler")
704 .to_string(),
705 description: body["Description"].as_str().unwrap_or("").to_string(),
706 timeout: body["Timeout"].as_i64().unwrap_or(3),
707 memory_size: body["MemorySize"].as_i64().unwrap_or(128),
708 package_type,
709 tags,
710 environment,
711 architectures,
712 code_zip,
713 code_fallback,
714 image_uri,
715 layer_arns,
716 tracing_mode,
717 kms_key_arn,
718 ephemeral_storage_size,
719 vpc_config,
720 snap_start,
721 dead_letter_config_arn,
722 file_system_configs,
723 logging_config,
724 image_config,
725 durable_config,
726 })
727 }
728}
729
730#[derive(Debug, Clone, Copy, PartialEq, Eq)]
732pub enum InvocationType {
733 RequestResponse,
734 Event,
735 DryRun,
736}
737
738impl InvocationType {
739 pub fn from_header(value: Option<&str>) -> Self {
740 match value {
741 Some("Event") => Self::Event,
742 Some("DryRun") => Self::DryRun,
743 _ => Self::RequestResponse,
744 }
745 }
746}
747
748fn route_to_destination(
752 bus: Arc<fakecloud_core::delivery::DeliveryBus>,
753 function_arn: &str,
754 request_payload: &[u8],
755 result: &Result<Vec<u8>, String>,
756 destination_config: Option<&serde_json::Value>,
757) {
758 let Some(cfg) = destination_config else {
759 return;
760 };
761 let (key, condition, response_value): (&str, &str, serde_json::Value) = match result {
762 Ok(bytes) => (
763 "OnSuccess",
764 "Success",
765 serde_json::from_slice(bytes).unwrap_or(serde_json::Value::Null),
766 ),
767 Err(err) => (
768 "OnFailure",
769 "RetriesExhausted",
770 serde_json::json!({ "errorMessage": err }),
771 ),
772 };
773 let Some(dest) = cfg
774 .get(key)
775 .and_then(|v| v.get("Destination"))
776 .and_then(|v| v.as_str())
777 else {
778 return;
779 };
780 let request_payload_v: serde_json::Value =
781 serde_json::from_slice(request_payload).unwrap_or(serde_json::Value::Null);
782 let record = serde_json::json!({
783 "version": "1.0",
784 "timestamp": chrono::Utc::now().to_rfc3339(),
785 "requestContext": {
786 "requestId": uuid::Uuid::new_v4().to_string(),
787 "functionArn": format!("{function_arn}:$LATEST"),
788 "condition": condition,
789 "approximateInvokeCount": 1,
790 },
791 "requestPayload": request_payload_v,
792 "responseContext": {
793 "statusCode": 200,
794 "executedVersion": "$LATEST",
795 },
796 "responsePayload": response_value,
797 });
798 let body = record.to_string();
799 if dest.contains(":sqs:") {
800 bus.send_to_sqs(dest, &body, &std::collections::HashMap::new());
801 } else if dest.contains(":sns:") {
802 bus.publish_to_sns(dest, &body, None);
803 } else if dest.contains(":lambda:") {
804 let dest = dest.to_string();
805 let payload = body.clone();
806 tokio::spawn(async move {
807 let _ = bus.invoke_lambda(&dest, &payload).await;
808 });
809 } else if dest.contains(":events:") || dest.contains(":eventbridge:") {
810 let detail_type = if result.is_ok() {
811 "Lambda Function Invocation Result - Success"
812 } else {
813 "Lambda Function Invocation Result - Failure"
814 };
815 bus.put_event_to_eventbridge("lambda", detail_type, &body, "default");
816 }
817}
818
819pub(crate) struct ConcurrencyGuard {
825 pub(crate) map: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
826 pub(crate) key: String,
827}
828
829impl Drop for ConcurrencyGuard {
830 fn drop(&mut self) {
831 let mut m = self.map.write();
832 let n = m.get(&self.key).copied().unwrap_or(0);
833 if n <= 1 {
834 m.remove(&self.key);
835 } else {
836 m.insert(self.key.clone(), n - 1);
837 }
838 }
839}
840
841fn function_config_unchanged_for_publish(
857 prev: &LambdaFunction,
858 live: &LambdaFunction,
859 effective_description: &str,
860) -> bool {
861 prev.code_sha256 == live.code_sha256
862 && prev.code_size == live.code_size
863 && prev.image_uri == live.image_uri
864 && prev.package_type == live.package_type
865 && prev.runtime == live.runtime
866 && prev.role == live.role
867 && prev.handler == live.handler
868 && prev.description == effective_description
869 && prev.timeout == live.timeout
870 && prev.memory_size == live.memory_size
871 && prev.environment == live.environment
872 && prev.architectures == live.architectures
873 && prev.layers.len() == live.layers.len()
874 && prev
875 .layers
876 .iter()
877 .zip(live.layers.iter())
878 .all(|(a, b)| a.arn == b.arn && a.code_size == b.code_size)
879 && prev.tracing_mode == live.tracing_mode
880 && prev.kms_key_arn == live.kms_key_arn
881 && prev.ephemeral_storage_size == live.ephemeral_storage_size
882 && prev.vpc_config == live.vpc_config
883 && prev.dead_letter_config_arn == live.dead_letter_config_arn
884 && prev.file_system_configs == live.file_system_configs
885 && prev.logging_config == live.logging_config
886 && prev.image_config == live.image_config
887 && prev.signing_profile_version_arn == live.signing_profile_version_arn
888 && prev.signing_job_arn == live.signing_job_arn
889 && prev.runtime_version_config == live.runtime_version_config
890 && snap_start_apply_on_eq(prev.snap_start.as_ref(), live.snap_start.as_ref())
891}
892
893fn snap_start_apply_on_eq(prev: Option<&Value>, live: Option<&Value>) -> bool {
902 let prev_apply = prev
903 .and_then(|v| v.get("ApplyOn"))
904 .and_then(|v| v.as_str())
905 .unwrap_or("None");
906 let live_apply = live
907 .and_then(|v| v.get("ApplyOn"))
908 .and_then(|v| v.as_str())
909 .unwrap_or("None");
910 prev_apply == live_apply
911}
912
913pub(crate) fn resolve_qualifier_to_version(
916 state: &LambdaState,
917 function_name: &str,
918 qualifier: Option<&str>,
919) -> Option<String> {
920 let q = qualifier?;
921 if q == "$LATEST" {
922 return None;
923 }
924 if q.chars().all(|c| c.is_ascii_digit()) {
925 return Some(q.to_string());
926 }
927 let alias_key = format!("{function_name}:{q}");
928 let alias = state.aliases.get(&alias_key)?;
929 let primary = alias.function_version.clone();
930 let routing = alias
931 .routing_config
932 .as_ref()
933 .and_then(|rc| rc.get("AdditionalVersionWeights"))
934 .and_then(|m| m.as_object());
935 let Some(weights) = routing else {
936 return Some(primary);
937 };
938 let mut additional: Vec<(String, f64)> = Vec::with_capacity(weights.len());
941 let mut sum: f64 = 0.0;
942 for (ver, w) in weights {
943 let weight = w.as_f64().unwrap_or(0.0).clamp(0.0, 1.0);
944 sum += weight;
945 additional.push((ver.clone(), weight));
946 }
947 let primary_weight = (1.0 - sum).max(0.0);
948 let pick: f64 = {
949 use std::cell::Cell;
954 thread_local! {
955 static RNG: Cell<u64> = const { Cell::new(0x9E37_79B9_7F4A_7C15) };
956 }
957 let now_nanos = std::time::SystemTime::now()
958 .duration_since(std::time::UNIX_EPOCH)
959 .map(|d| d.as_nanos() as u64)
960 .unwrap_or(0);
961 RNG.with(|cell| {
962 let mut s = cell.get() ^ now_nanos;
963 s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
965 let mut z = s;
966 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
967 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
968 z ^= z >> 31;
969 cell.set(s);
970 (z >> 11) as f64 / ((1u64 << 53) as f64)
971 })
972 };
973 let mut acc = primary_weight;
974 if pick < acc {
975 return Some(primary);
976 }
977 for (ver, w) in &additional {
978 acc += w;
979 if pick < acc {
980 return Some(ver.clone());
981 }
982 }
983 Some(primary)
984}
985
986pub struct LambdaService {
987 pub(crate) state: SharedLambdaState,
988 pub(crate) runtime: Option<Arc<ContainerRuntime>>,
989 snapshot_store: Option<Arc<dyn SnapshotStore>>,
990 snapshot_lock: Arc<AsyncMutex<()>>,
991 pub(crate) delivery_bus: Option<Arc<fakecloud_core::delivery::DeliveryBus>>,
992 pub(crate) role_trust_validator: Option<Arc<dyn fakecloud_core::auth::RoleTrustValidator>>,
993 pub(crate) s3_delivery: Option<Arc<dyn fakecloud_core::delivery::S3Delivery>>,
994 pub(crate) inflight_invocations: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
1001}
1002
1003mod functions;
1004mod init;
1005mod invoke;
1006mod publish;
1007
1008impl LambdaService {
1009 pub fn new(state: SharedLambdaState) -> Self {
1010 Self {
1011 state,
1012 runtime: None,
1013 snapshot_store: None,
1014 snapshot_lock: Arc::new(AsyncMutex::new(())),
1015 delivery_bus: None,
1016 role_trust_validator: None,
1017 s3_delivery: None,
1018 inflight_invocations: Arc::new(parking_lot::RwLock::new(BTreeMap::new())),
1019 }
1020 }
1021
1022 pub fn with_s3_delivery(mut self, s3: Arc<dyn fakecloud_core::delivery::S3Delivery>) -> Self {
1023 self.s3_delivery = Some(s3);
1024 self
1025 }
1026
1027 pub fn with_runtime(mut self, runtime: Arc<ContainerRuntime>) -> Self {
1028 self.runtime = Some(runtime);
1029 self
1030 }
1031
1032 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
1033 self.snapshot_store = Some(store);
1034 self
1035 }
1036
1037 pub fn with_delivery_bus(mut self, bus: Arc<fakecloud_core::delivery::DeliveryBus>) -> Self {
1038 self.delivery_bus = Some(bus);
1039 self
1040 }
1041
1042 pub fn with_role_trust_validator(
1043 mut self,
1044 validator: Arc<dyn fakecloud_core::auth::RoleTrustValidator>,
1045 ) -> Self {
1046 self.role_trust_validator = Some(validator);
1047 self
1048 }
1049
1050 async fn save_snapshot(&self) {
1051 save_lambda_snapshot(
1052 &self.state,
1053 self.snapshot_store.clone(),
1054 &self.snapshot_lock,
1055 )
1056 .await;
1057 }
1058
1059 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
1064 let store = self.snapshot_store.clone()?;
1065 let state = self.state.clone();
1066 let lock = self.snapshot_lock.clone();
1067 Some(Arc::new(move || {
1068 let state = state.clone();
1069 let store = store.clone();
1070 let lock = lock.clone();
1071 Box::pin(async move {
1072 save_lambda_snapshot(&state, Some(store), &lock).await;
1073 })
1074 }))
1075 }
1076}
1077
1078pub async fn save_lambda_snapshot(
1084 state: &SharedLambdaState,
1085 store: Option<Arc<dyn SnapshotStore>>,
1086 lock: &AsyncMutex<()>,
1087) {
1088 let Some(store) = store else {
1089 return;
1090 };
1091 let _guard = lock.lock().await;
1092 let snapshot = LambdaSnapshot {
1093 schema_version: LAMBDA_SNAPSHOT_SCHEMA_VERSION,
1094 accounts: Some(state.read().clone()),
1095 state: None,
1096 };
1097 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
1098 let bytes = serde_json::to_vec(&snapshot)
1099 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
1100 store.save(&bytes)
1101 })
1102 .await;
1103 match join {
1104 Ok(Ok(())) => {}
1105 Ok(Err(err)) => tracing::error!(%err, "failed to write lambda snapshot"),
1106 Err(err) => tracing::error!(%err, "lambda snapshot task panicked"),
1107 }
1108}
1109
1110#[async_trait]
1111impl AwsService for LambdaService {
1112 fn service_name(&self) -> &str {
1113 "lambda"
1114 }
1115
1116 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1117 let (action, resource_name) = Self::resolve_action(&req).ok_or_else(|| {
1118 const KNOWN_COLLECTIONS: &[&str] = &[
1129 "functions",
1130 "layers",
1131 "layers-by-arn",
1132 "event-source-mappings",
1133 "tags",
1134 "account-settings",
1135 "code-signing-configs",
1136 ];
1137 let is_known_collection = req
1138 .path_segments
1139 .get(1)
1140 .map(|s| KNOWN_COLLECTIONS.contains(&s.as_str()))
1141 .unwrap_or(false);
1142 if is_known_collection {
1143 AwsServiceError::aws_error(
1144 StatusCode::BAD_REQUEST,
1145 "InvalidParameterValueException",
1146 format!(
1147 "Could not route request {} {} — missing or invalid identifier",
1148 req.method, req.raw_path
1149 ),
1150 )
1151 } else {
1152 AwsServiceError::aws_error(
1153 StatusCode::NOT_FOUND,
1154 "UnknownOperationException",
1155 format!("Unknown operation: {} {}", req.method, req.raw_path),
1156 )
1157 }
1158 })?;
1159
1160 let arn_embedded_qualifier = resource_name
1168 .as_deref()
1169 .and_then(qualifier_from_function_ref);
1170 let resource_name = if action_takes_function_name(action) {
1171 if let Some(raw) = resource_name.as_ref() {
1178 let decoded = crate::extras::percent_decode_for_length(raw);
1183 let len = decoded.chars().count();
1184 let limit = if decoded.starts_with("arn:") {
1195 200
1196 } else {
1197 140
1198 };
1199 if decoded.is_empty() || len > limit {
1200 let (code, msg) = if action == "InvokeAsync" {
1201 (
1202 "ResourceNotFoundException",
1203 format!("Function not found: {}", raw),
1204 )
1205 } else {
1206 (
1207 "InvalidParameterValueException",
1208 format!(
1209 "1 validation error detected: Value '{}' at 'functionName' failed to \
1210 satisfy constraint: Member must have length less than or equal to 140",
1211 raw
1212 ),
1213 )
1214 };
1215 return Err(AwsServiceError::aws_error(
1216 if action == "InvokeAsync" {
1217 StatusCode::NOT_FOUND
1218 } else {
1219 StatusCode::BAD_REQUEST
1220 },
1221 code,
1222 msg,
1223 ));
1224 }
1225 }
1226 resource_name.map(|s| normalize_function_name(&s))
1227 } else {
1228 resource_name
1229 };
1230
1231 if let Some(raw) = req.query_params.get("MaxItems") {
1237 let n = raw.parse::<i64>().map_err(|_| {
1241 AwsServiceError::aws_error(
1242 StatusCode::BAD_REQUEST,
1243 "InvalidParameterValueException",
1244 format!("MaxItems must be a number (got '{raw}')"),
1245 )
1246 })?;
1247 let max = match action {
1251 "ListLayers"
1252 | "ListLayerVersions"
1253 | "ListFunctionUrlConfigs"
1254 | "ListProvisionedConcurrencyConfigs"
1255 | "ListFunctionEventInvokeConfigs" => 50,
1256 _ => 10000,
1257 };
1258 if !(1..=max).contains(&n) {
1259 return Err(AwsServiceError::aws_error(
1260 StatusCode::BAD_REQUEST,
1261 "InvalidParameterValueException",
1262 format!("MaxItems must be between 1 and {} (got {})", max, n),
1263 ));
1264 }
1265 }
1266
1267 if let Some(q) = req.query_params.get("Qualifier") {
1272 let len = q.chars().count();
1273 if q.is_empty() || len > 128 {
1274 return Err(AwsServiceError::aws_error(
1275 StatusCode::BAD_REQUEST,
1276 "InvalidParameterValueException",
1277 format!("Qualifier must be 1..128 characters (got length {})", len),
1278 ));
1279 }
1280 }
1281 if let Some(fv) = req.query_params.get("FunctionVersion") {
1284 let len = fv.chars().count();
1285 if fv.is_empty() || len > 1024 {
1286 return Err(AwsServiceError::aws_error(
1287 StatusCode::BAD_REQUEST,
1288 "InvalidParameterValueException",
1289 format!(
1290 "FunctionVersion must be 1..1024 characters (got length {})",
1291 len
1292 ),
1293 ));
1294 }
1295 }
1296
1297 let mutates = matches!(
1298 action,
1299 "CreateFunction"
1300 | "DeleteFunction"
1301 | "PublishVersion"
1302 | "AddPermission"
1303 | "RemovePermission"
1304 | "CreateEventSourceMapping"
1305 | "DeleteEventSourceMapping"
1306 | "UpdateEventSourceMapping"
1307 | "UpdateFunctionCode"
1308 | "UpdateFunctionConfiguration"
1309 | "CreateAlias"
1310 | "DeleteAlias"
1311 | "UpdateAlias"
1312 | "PublishLayerVersion"
1313 | "DeleteLayerVersion"
1314 | "AddLayerVersionPermission"
1315 | "RemoveLayerVersionPermission"
1316 | "CreateFunctionUrlConfig"
1317 | "DeleteFunctionUrlConfig"
1318 | "UpdateFunctionUrlConfig"
1319 | "PutFunctionConcurrency"
1320 | "DeleteFunctionConcurrency"
1321 | "PutProvisionedConcurrencyConfig"
1322 | "DeleteProvisionedConcurrencyConfig"
1323 | "CreateCodeSigningConfig"
1324 | "UpdateCodeSigningConfig"
1325 | "DeleteCodeSigningConfig"
1326 | "PutFunctionCodeSigningConfig"
1327 | "DeleteFunctionCodeSigningConfig"
1328 | "PutFunctionEventInvokeConfig"
1329 | "UpdateFunctionEventInvokeConfig"
1330 | "DeleteFunctionEventInvokeConfig"
1331 | "PutRuntimeManagementConfig"
1332 | "PutFunctionScalingConfig"
1333 | "PutFunctionRecursionConfig"
1334 | "TagResource"
1335 | "UntagResource"
1336 | "InvokeAsync"
1337 | "InvokeWithResponseStream"
1338 );
1339
1340 let aid = &req.account_id;
1341 prevalidate_lambda(action, &req)?;
1346 let result = match action {
1347 "CreateFunction" => self.create_function(&req),
1348 "ListFunctions" => self.list_functions(
1349 aid,
1350 req.query_params.get("FunctionVersion").map(String::as_str),
1351 req.query_params.get("Marker").map(String::as_str),
1352 marker_page_size(&req),
1353 ),
1354 "GetFunction" => self.get_function(
1355 &req,
1356 resource_name.as_deref().unwrap_or(""),
1357 aid,
1358 req.region.as_str(),
1359 req.query_params.get("Qualifier").map(String::as_str),
1360 ),
1361 "DeleteFunction" => self.delete_function(
1362 resource_name.as_deref().unwrap_or(""),
1363 aid,
1364 req.query_params.get("Qualifier").map(String::as_str),
1365 ),
1366 "Invoke" => {
1367 let invocation_type = InvocationType::from_header(
1368 req.headers
1369 .get("x-amz-invocation-type")
1370 .and_then(|v| v.to_str().ok()),
1371 );
1372 let log_tail = req
1373 .headers
1374 .get("x-amz-log-type")
1375 .and_then(|v| v.to_str().ok())
1376 == Some("Tail");
1377 let qualifier = req
1380 .query_params
1381 .get("Qualifier")
1382 .map(String::as_str)
1383 .or(arn_embedded_qualifier.as_deref());
1384 self.invoke(
1385 resource_name.as_deref().unwrap_or(""),
1386 &req.body,
1387 aid,
1388 invocation_type,
1389 qualifier,
1390 log_tail,
1391 )
1392 .await
1393 }
1394 "InvokeAsync" => {
1395 let name = resource_name.as_deref().unwrap_or("");
1401 let accounts = self.state.read();
1402 let exists = accounts
1403 .get(aid)
1404 .map(|s| s.functions.contains_key(name))
1405 .unwrap_or(false);
1406 if !exists {
1407 Err(AwsServiceError::aws_error(
1408 StatusCode::NOT_FOUND,
1409 "ResourceNotFoundException",
1410 format!("Function not found: {}", name),
1411 ))
1412 } else {
1413 Ok(AwsResponse::json(
1414 StatusCode::ACCEPTED,
1415 json!({ "Status": 202 }).to_string(),
1416 ))
1417 }
1418 }
1419 "PublishVersion" => {
1420 self.publish_version(resource_name.as_deref().unwrap_or(""), aid, &req)
1421 }
1422 "AddPermission" => self.add_permission(resource_name.as_deref().unwrap_or(""), &req),
1423 "GetPolicy" => self.get_policy(
1424 resource_name.as_deref().unwrap_or(""),
1425 aid,
1426 req.query_params.get("Qualifier").map(String::as_str),
1427 ),
1428 "RemovePermission" => {
1429 let sid = req.path_segments.get(4).cloned().unwrap_or_default();
1431 self.remove_permission(
1432 resource_name.as_deref().unwrap_or(""),
1433 &sid,
1434 aid,
1435 req.query_params.get("Qualifier").map(String::as_str),
1436 )
1437 }
1438 "CreateEventSourceMapping" => self.create_event_source_mapping(&req),
1439 "ListEventSourceMappings" => {
1440 if let Some(fn_name) = req.query_params.get("FunctionName") {
1444 let len = fn_name.chars().count();
1445 if fn_name.is_empty() || len > 140 {
1446 return Err(AwsServiceError::aws_error(
1447 StatusCode::BAD_REQUEST,
1448 "InvalidParameterValueException",
1449 "FunctionName must be 1..140 characters",
1450 ));
1451 }
1452 }
1453 self.list_event_source_mappings(aid, &req)
1454 }
1455 "GetEventSourceMapping" => {
1456 self.get_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1457 }
1458 "DeleteEventSourceMapping" => {
1459 self.delete_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1460 }
1461 "CreateCapacityProvider" => {
1462 crate::workflows::create_capacity_provider(&self.state, &req, &req.json_body())
1463 }
1464 "GetCapacityProvider" => crate::workflows::get_capacity_provider(
1465 &self.state,
1466 &req,
1467 resource_name.as_deref().unwrap_or(""),
1468 ),
1469 "ListCapacityProviders" => crate::workflows::list_capacity_providers(&self.state, &req),
1470 "UpdateCapacityProvider" => crate::workflows::update_capacity_provider(
1471 &self.state,
1472 &req,
1473 resource_name.as_deref().unwrap_or(""),
1474 &req.json_body(),
1475 ),
1476 "DeleteCapacityProvider" => crate::workflows::delete_capacity_provider(
1477 &self.state,
1478 &req,
1479 resource_name.as_deref().unwrap_or(""),
1480 ),
1481 "ListFunctionVersionsByCapacityProvider" => {
1482 crate::workflows::list_function_versions_by_capacity_provider(
1483 &self.state,
1484 &req,
1485 resource_name.as_deref().unwrap_or(""),
1486 )
1487 }
1488 "GetDurableExecution" => crate::workflows::get_durable_execution(
1489 &self.state,
1490 &req,
1491 resource_name.as_deref().unwrap_or(""),
1492 ),
1493 "GetDurableExecutionHistory" => crate::workflows::get_durable_execution_history(
1494 &self.state,
1495 &req,
1496 resource_name.as_deref().unwrap_or(""),
1497 ),
1498 "GetDurableExecutionState" => crate::workflows::get_durable_execution_state(
1499 &self.state,
1500 &req,
1501 resource_name.as_deref().unwrap_or(""),
1502 ),
1503 "ListDurableExecutionsByFunction" => {
1504 crate::workflows::list_durable_executions_by_function(
1505 &self.state,
1506 &req,
1507 resource_name.as_deref().unwrap_or(""),
1508 )
1509 }
1510 "CheckpointDurableExecution" => crate::workflows::checkpoint_durable_execution(
1511 &self.state,
1512 &req,
1513 resource_name.as_deref().unwrap_or(""),
1514 &req.json_body(),
1515 ),
1516 "StopDurableExecution" => crate::workflows::stop_durable_execution(
1517 &self.state,
1518 &req,
1519 resource_name.as_deref().unwrap_or(""),
1520 ),
1521 "SendDurableExecutionCallbackSuccess" => crate::workflows::send_callback_success(
1522 &self.state,
1523 &req,
1524 resource_name.as_deref().unwrap_or(""),
1525 ),
1526 "SendDurableExecutionCallbackFailure" => crate::workflows::send_callback_failure(
1527 &self.state,
1528 &req,
1529 resource_name.as_deref().unwrap_or(""),
1530 ),
1531 "SendDurableExecutionCallbackHeartbeat" => crate::workflows::send_callback_heartbeat(
1532 &self.state,
1533 &req,
1534 resource_name.as_deref().unwrap_or(""),
1535 ),
1536 other => {
1537 self.handle_extra(other, resource_name.as_deref(), &req)
1538 .await
1539 }
1540 };
1541 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
1542 self.save_snapshot().await;
1543 }
1544 result
1545 }
1546
1547 fn supported_actions(&self) -> &[&str] {
1548 &[
1549 "CreateFunction",
1550 "GetFunction",
1551 "DeleteFunction",
1552 "ListFunctions",
1553 "Invoke",
1554 "InvokeAsync",
1555 "InvokeWithResponseStream",
1556 "PublishVersion",
1557 "ListVersionsByFunction",
1558 "AddPermission",
1559 "RemovePermission",
1560 "GetPolicy",
1561 "CreateEventSourceMapping",
1562 "ListEventSourceMappings",
1563 "GetEventSourceMapping",
1564 "UpdateEventSourceMapping",
1565 "DeleteEventSourceMapping",
1566 "GetFunctionConfiguration",
1567 "UpdateFunctionConfiguration",
1568 "UpdateFunctionCode",
1569 "GetAccountSettings",
1570 "CreateAlias",
1571 "GetAlias",
1572 "ListAliases",
1573 "UpdateAlias",
1574 "DeleteAlias",
1575 "PublishLayerVersion",
1576 "GetLayerVersion",
1577 "GetLayerVersionByArn",
1578 "DeleteLayerVersion",
1579 "ListLayerVersions",
1580 "ListLayers",
1581 "GetLayerVersionPolicy",
1582 "AddLayerVersionPermission",
1583 "RemoveLayerVersionPermission",
1584 "CreateFunctionUrlConfig",
1585 "GetFunctionUrlConfig",
1586 "UpdateFunctionUrlConfig",
1587 "DeleteFunctionUrlConfig",
1588 "ListFunctionUrlConfigs",
1589 "PutFunctionConcurrency",
1590 "GetFunctionConcurrency",
1591 "DeleteFunctionConcurrency",
1592 "PutProvisionedConcurrencyConfig",
1593 "GetProvisionedConcurrencyConfig",
1594 "DeleteProvisionedConcurrencyConfig",
1595 "ListProvisionedConcurrencyConfigs",
1596 "CreateCodeSigningConfig",
1597 "GetCodeSigningConfig",
1598 "UpdateCodeSigningConfig",
1599 "DeleteCodeSigningConfig",
1600 "ListCodeSigningConfigs",
1601 "PutFunctionCodeSigningConfig",
1602 "GetFunctionCodeSigningConfig",
1603 "DeleteFunctionCodeSigningConfig",
1604 "ListFunctionsByCodeSigningConfig",
1605 "PutFunctionEventInvokeConfig",
1606 "GetFunctionEventInvokeConfig",
1607 "UpdateFunctionEventInvokeConfig",
1608 "DeleteFunctionEventInvokeConfig",
1609 "ListFunctionEventInvokeConfigs",
1610 "PutRuntimeManagementConfig",
1611 "GetRuntimeManagementConfig",
1612 "PutFunctionScalingConfig",
1613 "GetFunctionScalingConfig",
1614 "PutFunctionRecursionConfig",
1615 "GetFunctionRecursionConfig",
1616 "TagResource",
1617 "UntagResource",
1618 "ListTags",
1619 "CreateCapacityProvider",
1620 "GetCapacityProvider",
1621 "ListCapacityProviders",
1622 "UpdateCapacityProvider",
1623 "DeleteCapacityProvider",
1624 "ListFunctionVersionsByCapacityProvider",
1625 "GetDurableExecution",
1626 "GetDurableExecutionHistory",
1627 "GetDurableExecutionState",
1628 "ListDurableExecutionsByFunction",
1629 "CheckpointDurableExecution",
1630 "StopDurableExecution",
1631 "SendDurableExecutionCallbackSuccess",
1632 "SendDurableExecutionCallbackFailure",
1633 "SendDurableExecutionCallbackHeartbeat",
1634 ]
1635 }
1636
1637 fn iam_enforceable(&self) -> bool {
1638 true
1639 }
1640
1641 fn iam_action_for(&self, request: &AwsRequest) -> Option<fakecloud_core::auth::IamAction> {
1645 let (action_str, resource_name) = Self::resolve_action(request)?;
1650 let action = iam_action_name_for(action_str)?;
1658 let accounts = self.state.read();
1659 let empty = LambdaState::new(&request.account_id, &request.region);
1660 let state = accounts.get(&request.account_id).unwrap_or(&empty);
1661 let resource = match action_str {
1662 "GetFunction"
1667 | "DeleteFunction"
1668 | "Invoke"
1669 | "InvokeAsync"
1670 | "InvokeWithResponseStream"
1671 | "PublishVersion"
1672 | "ListVersionsByFunction"
1673 | "AddPermission"
1674 | "RemovePermission"
1675 | "GetPolicy"
1676 | "GetFunctionConfiguration"
1677 | "UpdateFunctionConfiguration"
1678 | "UpdateFunctionCode"
1679 | "CreateAlias"
1680 | "GetAlias"
1681 | "UpdateAlias"
1682 | "DeleteAlias"
1683 | "ListAliases"
1684 | "PutFunctionConcurrency"
1685 | "GetFunctionConcurrency"
1686 | "DeleteFunctionConcurrency"
1687 | "PutProvisionedConcurrencyConfig"
1688 | "GetProvisionedConcurrencyConfig"
1689 | "DeleteProvisionedConcurrencyConfig"
1690 | "ListProvisionedConcurrencyConfigs"
1691 | "PutFunctionEventInvokeConfig"
1692 | "GetFunctionEventInvokeConfig"
1693 | "UpdateFunctionEventInvokeConfig"
1694 | "DeleteFunctionEventInvokeConfig"
1695 | "ListFunctionEventInvokeConfigs"
1696 | "PutRuntimeManagementConfig"
1697 | "GetRuntimeManagementConfig"
1698 | "PutFunctionScalingConfig"
1699 | "GetFunctionScalingConfig"
1700 | "PutFunctionRecursionConfig"
1701 | "GetFunctionRecursionConfig"
1702 | "PutFunctionCodeSigningConfig"
1703 | "GetFunctionCodeSigningConfig"
1704 | "DeleteFunctionCodeSigningConfig"
1705 | "CreateFunctionUrlConfig"
1706 | "GetFunctionUrlConfig"
1707 | "UpdateFunctionUrlConfig"
1708 | "DeleteFunctionUrlConfig"
1709 | "ListFunctionUrlConfigs"
1710 | "ListDurableExecutionsByFunction" => {
1711 let raw = resource_name.unwrap_or_default();
1712 if raw.is_empty() {
1713 "*".to_string()
1714 } else {
1715 let name = normalize_function_name(&raw);
1721 format!(
1722 "arn:aws:lambda:{}:{}:function:{}",
1723 state.region, state.account_id, name
1724 )
1725 }
1726 }
1727 "CreateFunction" => {
1728 serde_json::from_slice::<Value>(&request.body)
1733 .ok()
1734 .and_then(|v| {
1735 v.get("FunctionName").and_then(|f| f.as_str()).map(|n| {
1736 format!(
1737 "arn:aws:lambda:{}:{}:function:{}",
1738 state.region, state.account_id, n
1739 )
1740 })
1741 })
1742 .unwrap_or_else(|| "*".to_string())
1743 }
1744 _ => "*".to_string(),
1745 };
1746 Some(fakecloud_core::auth::IamAction {
1747 service: "lambda",
1748 action,
1749 resource,
1750 })
1751 }
1752
1753 fn iam_condition_keys_for(
1754 &self,
1755 request: &AwsRequest,
1756 action: &fakecloud_core::auth::IamAction,
1757 ) -> std::collections::BTreeMap<String, Vec<String>> {
1758 let mut out = std::collections::BTreeMap::new();
1759 if action.action == "AddPermission" {
1760 if action.resource != "*" {
1761 out.insert(
1762 "lambda:functionarn".to_string(),
1763 vec![action.resource.clone()],
1764 );
1765 }
1766 if let Ok(body) = serde_json::from_slice::<Value>(&request.body) {
1767 if let Some(principal) = body.get("Principal").and_then(|p| p.as_str()) {
1768 out.insert("lambda:principal".to_string(), vec![principal.to_string()]);
1769 }
1770 }
1771 }
1772 out
1773 }
1774}
1775
1776#[path = "../service_event_sources.rs"]
1777mod service_event_sources;
1778#[path = "../service_permissions.rs"]
1779mod service_permissions;
1780
1781#[cfg(test)]
1782#[path = "../service_tests.rs"]
1783mod tests;