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
20pub(crate) fn action_takes_function_name(action: &str) -> bool {
25 matches!(
26 action,
27 "GetFunction"
28 | "DeleteFunction"
29 | "Invoke"
30 | "InvokeAsync"
31 | "InvokeWithResponseStream"
32 | "PublishVersion"
33 | "ListVersionsByFunction"
34 | "AddPermission"
35 | "RemovePermission"
36 | "GetPolicy"
37 | "GetFunctionConfiguration"
38 | "UpdateFunctionConfiguration"
39 | "UpdateFunctionCode"
40 | "GetFunctionConcurrency"
41 | "PutFunctionConcurrency"
42 | "DeleteFunctionConcurrency"
43 | "PutProvisionedConcurrencyConfig"
44 | "GetProvisionedConcurrencyConfig"
45 | "DeleteProvisionedConcurrencyConfig"
46 | "ListProvisionedConcurrencyConfigs"
47 | "PutFunctionEventInvokeConfig"
48 | "UpdateFunctionEventInvokeConfig"
49 | "GetFunctionEventInvokeConfig"
50 | "DeleteFunctionEventInvokeConfig"
51 | "ListFunctionEventInvokeConfigs"
52 | "CreateFunctionUrlConfig"
53 | "UpdateFunctionUrlConfig"
54 | "GetFunctionUrlConfig"
55 | "DeleteFunctionUrlConfig"
56 | "ListFunctionUrlConfigs"
57 | "PutFunctionCodeSigningConfig"
58 | "GetFunctionCodeSigningConfig"
59 | "DeleteFunctionCodeSigningConfig"
60 | "GetFunctionScalingConfig"
61 | "PutFunctionScalingConfig"
62 | "PutFunctionRecursionConfig"
63 | "GetFunctionRecursionConfig"
64 | "CreateAlias"
65 | "GetAlias"
66 | "ListAliases"
67 | "UpdateAlias"
68 | "DeleteAlias"
69 | "PutRuntimeManagementConfig"
70 | "GetRuntimeManagementConfig"
71 )
72}
73
74pub(crate) fn normalize_function_name(input: &str) -> String {
89 if input.is_empty() {
90 return String::new();
91 }
92
93 let decoded = percent_encoding::percent_decode_str(input)
98 .decode_utf8_lossy()
99 .into_owned();
100 let input = decoded.as_str();
101
102 if let Some(rest) = input.strip_prefix("arn:aws:lambda:") {
104 let parts: Vec<&str> = rest.splitn(5, ':').collect();
105 if parts.len() >= 4 && parts[2] == "function" && !parts[3].is_empty() {
107 return parts[3].to_string();
108 }
109 return input.to_string();
110 }
111
112 let parts: Vec<&str> = input.splitn(4, ':').collect();
114 if parts.len() >= 3 && parts[1] == "function" && parts[0].chars().all(|c| c.is_ascii_digit()) {
115 if !parts[2].is_empty() {
116 return parts[2].to_string();
117 }
118 return input.to_string();
119 }
120
121 if input.matches(':').count() == 1 {
127 if let Some((name, _qualifier)) = input.split_once(':') {
128 if !name.is_empty() && name.chars().all(is_function_name_char) {
129 return name.to_string();
130 }
131 }
132 }
133
134 input.to_string()
135}
136
137fn is_function_name_char(c: char) -> bool {
138 c.is_ascii_alphanumeric() || c == '-' || c == '_'
139}
140
141pub(crate) fn validate_ephemeral_storage(size: i64) -> Result<i64, AwsServiceError> {
146 if !(512..=10240).contains(&size) {
147 return Err(AwsServiceError::aws_error(
148 StatusCode::BAD_REQUEST,
149 "InvalidParameterValueException",
150 format!(
151 "Value {size} at 'ephemeralStorage.size' failed to satisfy constraint: \
152 Member must satisfy constraint: [Member must have value less than or equal to 10240, \
153 Member must have value greater than or equal to 512]"
154 ),
155 ));
156 }
157 Ok(size)
158}
159
160struct CreateFunctionInput {
164 function_name: String,
165 runtime: String,
166 role: String,
167 handler: String,
168 description: String,
169 timeout: i64,
170 memory_size: i64,
171 package_type: String,
172 tags: BTreeMap<String, String>,
173 environment: BTreeMap<String, String>,
174 architectures: Vec<String>,
175 code_zip: Option<Vec<u8>>,
176 code_fallback: Vec<u8>,
177 image_uri: Option<String>,
178 layer_arns: Vec<String>,
179 tracing_mode: Option<String>,
180 kms_key_arn: Option<String>,
181 ephemeral_storage_size: Option<i64>,
182 vpc_config: Option<serde_json::Value>,
183 snap_start: Option<serde_json::Value>,
184 dead_letter_config_arn: Option<String>,
185 file_system_configs: Vec<serde_json::Value>,
186 logging_config: Option<serde_json::Value>,
187 image_config: Option<serde_json::Value>,
188 durable_config: Option<serde_json::Value>,
189}
190
191impl CreateFunctionInput {
192 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
193 let function_name = body["FunctionName"]
194 .as_str()
195 .ok_or_else(|| {
196 AwsServiceError::aws_error(
197 StatusCode::BAD_REQUEST,
198 "InvalidParameterValueException",
199 "FunctionName is required",
200 )
201 })?
202 .to_string();
203
204 let tags: BTreeMap<String, String> = body["Tags"]
205 .as_object()
206 .map(|m| {
207 m.iter()
208 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
209 .collect()
210 })
211 .unwrap_or_default();
212
213 let environment: BTreeMap<String, String> = body["Environment"]["Variables"]
214 .as_object()
215 .map(|m| {
216 m.iter()
217 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
218 .collect()
219 })
220 .unwrap_or_default();
221
222 let architectures = body["Architectures"]
223 .as_array()
224 .map(|a| {
225 a.iter()
226 .filter_map(|v| v.as_str().map(|s| s.to_string()))
227 .collect()
228 })
229 .unwrap_or_else(|| vec!["x86_64".to_string()]);
230
231 let code_zip: Option<Vec<u8>> = match body["Code"]["ZipFile"].as_str() {
232 Some(b64) => Some(
233 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64).map_err(
234 |_| {
235 AwsServiceError::aws_error(
236 StatusCode::BAD_REQUEST,
237 "InvalidParameterValueException",
238 "Could not decode Code.ZipFile: invalid base64",
239 )
240 },
241 )?,
242 ),
243 None => None,
244 };
245
246 let code_fallback = serde_json::to_vec(&body["Code"]).unwrap_or_default();
247
248 let package_type = body["PackageType"].as_str().unwrap_or("Zip").to_string();
249 let image_uri = if package_type == "Image" {
254 body["Code"]["ImageUri"].as_str().map(String::from)
255 } else {
256 None
257 };
258
259 if package_type == "Image" && image_uri.is_none() {
263 return Err(AwsServiceError::aws_error(
264 StatusCode::BAD_REQUEST,
265 "InvalidParameterValueException",
266 "Code.ImageUri is required when PackageType is Image",
267 ));
268 }
269
270 let layer_arns: Vec<String> = body["Layers"]
271 .as_array()
272 .map(|arr| {
273 arr.iter()
274 .filter_map(|v| v.as_str().map(String::from))
275 .collect()
276 })
277 .unwrap_or_default();
278
279 let tracing_mode = body["TracingConfig"]["Mode"].as_str().map(String::from);
280 let kms_key_arn = body["KMSKeyArn"].as_str().map(String::from);
281 let ephemeral_storage_size = match body["EphemeralStorage"]["Size"].as_i64() {
282 Some(size) => Some(validate_ephemeral_storage(size)?),
283 None => None,
284 };
285 let vpc_config = body["VpcConfig"]
286 .is_object()
287 .then(|| body["VpcConfig"].clone());
288 let snap_start = body["SnapStart"]
289 .is_object()
290 .then(|| body["SnapStart"].clone());
291 let dead_letter_config_arn = body["DeadLetterConfig"]["TargetArn"]
292 .as_str()
293 .map(String::from);
294 let file_system_configs = body["FileSystemConfigs"]
295 .as_array()
296 .cloned()
297 .unwrap_or_default();
298 let logging_config = body["LoggingConfig"]
299 .is_object()
300 .then(|| body["LoggingConfig"].clone());
301 let image_config = body["ImageConfig"]
302 .is_object()
303 .then(|| body["ImageConfig"].clone());
304 let durable_config = body["DurableConfig"]
305 .is_object()
306 .then(|| body["DurableConfig"].clone());
307
308 Ok(Self {
309 function_name,
310 runtime: body["Runtime"].as_str().unwrap_or("python3.12").to_string(),
311 role: body["Role"].as_str().unwrap_or("").to_string(),
312 handler: body["Handler"]
313 .as_str()
314 .unwrap_or("index.handler")
315 .to_string(),
316 description: body["Description"].as_str().unwrap_or("").to_string(),
317 timeout: body["Timeout"].as_i64().unwrap_or(3),
318 memory_size: body["MemorySize"].as_i64().unwrap_or(128),
319 package_type,
320 tags,
321 environment,
322 architectures,
323 code_zip,
324 code_fallback,
325 image_uri,
326 layer_arns,
327 tracing_mode,
328 kms_key_arn,
329 ephemeral_storage_size,
330 vpc_config,
331 snap_start,
332 dead_letter_config_arn,
333 file_system_configs,
334 logging_config,
335 image_config,
336 durable_config,
337 })
338 }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum InvocationType {
344 RequestResponse,
345 Event,
346 DryRun,
347}
348
349impl InvocationType {
350 pub fn from_header(value: Option<&str>) -> Self {
351 match value {
352 Some("Event") => Self::Event,
353 Some("DryRun") => Self::DryRun,
354 _ => Self::RequestResponse,
355 }
356 }
357}
358
359fn route_to_destination(
363 bus: Arc<fakecloud_core::delivery::DeliveryBus>,
364 function_arn: &str,
365 request_payload: &[u8],
366 result: &Result<Vec<u8>, String>,
367 destination_config: Option<&serde_json::Value>,
368) {
369 let Some(cfg) = destination_config else {
370 return;
371 };
372 let (key, condition, response_value): (&str, &str, serde_json::Value) = match result {
373 Ok(bytes) => (
374 "OnSuccess",
375 "Success",
376 serde_json::from_slice(bytes).unwrap_or(serde_json::Value::Null),
377 ),
378 Err(err) => (
379 "OnFailure",
380 "RetriesExhausted",
381 serde_json::json!({ "errorMessage": err }),
382 ),
383 };
384 let Some(dest) = cfg
385 .get(key)
386 .and_then(|v| v.get("Destination"))
387 .and_then(|v| v.as_str())
388 else {
389 return;
390 };
391 let request_payload_v: serde_json::Value =
392 serde_json::from_slice(request_payload).unwrap_or(serde_json::Value::Null);
393 let record = serde_json::json!({
394 "version": "1.0",
395 "timestamp": chrono::Utc::now().to_rfc3339(),
396 "requestContext": {
397 "requestId": uuid::Uuid::new_v4().to_string(),
398 "functionArn": format!("{function_arn}:$LATEST"),
399 "condition": condition,
400 "approximateInvokeCount": 1,
401 },
402 "requestPayload": request_payload_v,
403 "responseContext": {
404 "statusCode": 200,
405 "executedVersion": "$LATEST",
406 },
407 "responsePayload": response_value,
408 });
409 let body = record.to_string();
410 if dest.contains(":sqs:") {
411 bus.send_to_sqs(dest, &body, &std::collections::HashMap::new());
412 } else if dest.contains(":sns:") {
413 bus.publish_to_sns(dest, &body, None);
414 } else if dest.contains(":lambda:") {
415 let dest = dest.to_string();
416 let payload = body.clone();
417 tokio::spawn(async move {
418 let _ = bus.invoke_lambda(&dest, &payload).await;
419 });
420 } else if dest.contains(":events:") || dest.contains(":eventbridge:") {
421 let detail_type = if result.is_ok() {
422 "Lambda Function Invocation Result - Success"
423 } else {
424 "Lambda Function Invocation Result - Failure"
425 };
426 bus.put_event_to_eventbridge("lambda", detail_type, &body, "default");
427 }
428}
429
430pub(crate) struct ConcurrencyGuard {
436 pub(crate) map: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
437 pub(crate) key: String,
438}
439
440impl Drop for ConcurrencyGuard {
441 fn drop(&mut self) {
442 let mut m = self.map.write();
443 let n = m.get(&self.key).copied().unwrap_or(0);
444 if n <= 1 {
445 m.remove(&self.key);
446 } else {
447 m.insert(self.key.clone(), n - 1);
448 }
449 }
450}
451
452fn function_config_unchanged_for_publish(
468 prev: &LambdaFunction,
469 live: &LambdaFunction,
470 effective_description: &str,
471) -> bool {
472 prev.code_sha256 == live.code_sha256
473 && prev.code_size == live.code_size
474 && prev.image_uri == live.image_uri
475 && prev.package_type == live.package_type
476 && prev.runtime == live.runtime
477 && prev.role == live.role
478 && prev.handler == live.handler
479 && prev.description == effective_description
480 && prev.timeout == live.timeout
481 && prev.memory_size == live.memory_size
482 && prev.environment == live.environment
483 && prev.architectures == live.architectures
484 && prev.layers.len() == live.layers.len()
485 && prev
486 .layers
487 .iter()
488 .zip(live.layers.iter())
489 .all(|(a, b)| a.arn == b.arn && a.code_size == b.code_size)
490 && prev.tracing_mode == live.tracing_mode
491 && prev.kms_key_arn == live.kms_key_arn
492 && prev.ephemeral_storage_size == live.ephemeral_storage_size
493 && prev.vpc_config == live.vpc_config
494 && prev.dead_letter_config_arn == live.dead_letter_config_arn
495 && prev.file_system_configs == live.file_system_configs
496 && prev.logging_config == live.logging_config
497 && prev.image_config == live.image_config
498 && prev.signing_profile_version_arn == live.signing_profile_version_arn
499 && prev.signing_job_arn == live.signing_job_arn
500 && prev.runtime_version_config == live.runtime_version_config
501 && snap_start_apply_on_eq(prev.snap_start.as_ref(), live.snap_start.as_ref())
502}
503
504fn snap_start_apply_on_eq(prev: Option<&Value>, live: Option<&Value>) -> bool {
513 let prev_apply = prev
514 .and_then(|v| v.get("ApplyOn"))
515 .and_then(|v| v.as_str())
516 .unwrap_or("None");
517 let live_apply = live
518 .and_then(|v| v.get("ApplyOn"))
519 .and_then(|v| v.as_str())
520 .unwrap_or("None");
521 prev_apply == live_apply
522}
523
524pub(crate) fn resolve_qualifier_to_version(
527 state: &LambdaState,
528 function_name: &str,
529 qualifier: Option<&str>,
530) -> Option<String> {
531 let q = qualifier?;
532 if q == "$LATEST" {
533 return None;
534 }
535 if q.chars().all(|c| c.is_ascii_digit()) {
536 return Some(q.to_string());
537 }
538 let alias_key = format!("{function_name}:{q}");
539 let alias = state.aliases.get(&alias_key)?;
540 let primary = alias.function_version.clone();
541 let routing = alias
542 .routing_config
543 .as_ref()
544 .and_then(|rc| rc.get("AdditionalVersionWeights"))
545 .and_then(|m| m.as_object());
546 let Some(weights) = routing else {
547 return Some(primary);
548 };
549 let mut additional: Vec<(String, f64)> = Vec::with_capacity(weights.len());
552 let mut sum: f64 = 0.0;
553 for (ver, w) in weights {
554 let weight = w.as_f64().unwrap_or(0.0).clamp(0.0, 1.0);
555 sum += weight;
556 additional.push((ver.clone(), weight));
557 }
558 let primary_weight = (1.0 - sum).max(0.0);
559 let pick: f64 = {
560 use std::cell::Cell;
565 thread_local! {
566 static RNG: Cell<u64> = const { Cell::new(0x9E37_79B9_7F4A_7C15) };
567 }
568 let now_nanos = std::time::SystemTime::now()
569 .duration_since(std::time::UNIX_EPOCH)
570 .map(|d| d.as_nanos() as u64)
571 .unwrap_or(0);
572 RNG.with(|cell| {
573 let mut s = cell.get() ^ now_nanos;
574 s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
576 let mut z = s;
577 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
578 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
579 z ^= z >> 31;
580 cell.set(s);
581 (z >> 11) as f64 / ((1u64 << 53) as f64)
582 })
583 };
584 let mut acc = primary_weight;
585 if pick < acc {
586 return Some(primary);
587 }
588 for (ver, w) in &additional {
589 acc += w;
590 if pick < acc {
591 return Some(ver.clone());
592 }
593 }
594 Some(primary)
595}
596
597pub struct LambdaService {
598 pub(crate) state: SharedLambdaState,
599 pub(crate) runtime: Option<Arc<ContainerRuntime>>,
600 snapshot_store: Option<Arc<dyn SnapshotStore>>,
601 snapshot_lock: Arc<AsyncMutex<()>>,
602 pub(crate) delivery_bus: Option<Arc<fakecloud_core::delivery::DeliveryBus>>,
603 pub(crate) role_trust_validator: Option<Arc<dyn fakecloud_core::auth::RoleTrustValidator>>,
604 pub(crate) s3_delivery: Option<Arc<dyn fakecloud_core::delivery::S3Delivery>>,
605 pub(crate) inflight_invocations: Arc<parking_lot::RwLock<BTreeMap<String, i64>>>,
612}
613
614impl LambdaService {
615 pub fn new(state: SharedLambdaState) -> Self {
616 Self {
617 state,
618 runtime: None,
619 snapshot_store: None,
620 snapshot_lock: Arc::new(AsyncMutex::new(())),
621 delivery_bus: None,
622 role_trust_validator: None,
623 s3_delivery: None,
624 inflight_invocations: Arc::new(parking_lot::RwLock::new(BTreeMap::new())),
625 }
626 }
627
628 pub fn with_s3_delivery(mut self, s3: Arc<dyn fakecloud_core::delivery::S3Delivery>) -> Self {
629 self.s3_delivery = Some(s3);
630 self
631 }
632
633 pub fn with_runtime(mut self, runtime: Arc<ContainerRuntime>) -> Self {
634 self.runtime = Some(runtime);
635 self
636 }
637
638 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
639 self.snapshot_store = Some(store);
640 self
641 }
642
643 pub fn with_delivery_bus(mut self, bus: Arc<fakecloud_core::delivery::DeliveryBus>) -> Self {
644 self.delivery_bus = Some(bus);
645 self
646 }
647
648 pub fn with_role_trust_validator(
649 mut self,
650 validator: Arc<dyn fakecloud_core::auth::RoleTrustValidator>,
651 ) -> Self {
652 self.role_trust_validator = Some(validator);
653 self
654 }
655
656 async fn save_snapshot(&self) {
657 let Some(store) = self.snapshot_store.clone() else {
658 return;
659 };
660 let _guard = self.snapshot_lock.lock().await;
661 let snapshot = LambdaSnapshot {
662 schema_version: LAMBDA_SNAPSHOT_SCHEMA_VERSION,
663 accounts: Some(self.state.read().clone()),
664 state: None,
665 };
666 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
667 let bytes = serde_json::to_vec(&snapshot)
668 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
669 store.save(&bytes)
670 })
671 .await;
672 match join {
673 Ok(Ok(())) => {}
674 Ok(Err(err)) => tracing::error!(%err, "failed to write lambda snapshot"),
675 Err(err) => tracing::error!(%err, "lambda snapshot task panicked"),
676 }
677 }
678
679 fn resolve_action(req: &AwsRequest) -> Option<(&'static str, Option<String>)> {
692 let segs = &req.path_segments;
693 if segs.is_empty() {
694 return None;
695 }
696 let prefix = segs[0].as_str();
700
701 if segs.get(1).map(|s| s.as_str()) == Some("account-settings") && req.method == Method::GET
703 {
704 return Some(("GetAccountSettings", None));
705 }
706 if segs.get(1).map(|s| s.as_str()) == Some("functions")
707 && segs.get(3).map(|s| s.as_str()) == Some("invoke-async")
708 && req.method == Method::POST
709 {
710 return Some(("InvokeAsync", segs.get(2).map(|s| s.to_string())));
711 }
712 if segs.get(1).map(|s| s.as_str()) == Some("functions")
713 && segs.get(3).map(|s| s.as_str()) == Some("response-streaming-invocations")
714 && req.method == Method::POST
715 {
716 return Some((
717 "InvokeWithResponseStream",
718 segs.get(2).map(|s| s.to_string()),
719 ));
720 }
721
722 if segs.get(1).map(|s| s.as_str()) == Some("functions")
724 && segs.get(3).map(|s| s.as_str()) == Some("concurrency")
725 {
726 let res = segs.get(2).map(|s| s.to_string());
727 return match req.method {
728 Method::PUT => Some(("PutFunctionConcurrency", res)),
729 Method::GET => Some(("GetFunctionConcurrency", res)),
730 Method::DELETE => Some(("DeleteFunctionConcurrency", res)),
731 _ => None,
732 };
733 }
734
735 if segs.get(1).map(|s| s.as_str()) == Some("functions")
743 && segs.get(3).map(|s| s.as_str()) == Some("provisioned-concurrency")
744 {
745 let res = segs.get(2).map(|s| s.to_string());
746 if req.method == Method::GET && req.query_params.contains_key("List") {
747 return Some(("ListProvisionedConcurrencyConfigs", res));
748 }
749 return match req.method {
750 Method::PUT => Some(("PutProvisionedConcurrencyConfig", res)),
751 Method::GET => Some(("GetProvisionedConcurrencyConfig", res)),
752 Method::DELETE => Some(("DeleteProvisionedConcurrencyConfig", res)),
753 _ => None,
754 };
755 }
756 if segs.get(1).map(|s| s.as_str()) == Some("functions")
760 && segs.get(3).map(|s| s.as_str()) == Some("provisioned-concurrency-configs")
761 && req.method == Method::GET
762 {
763 return Some((
764 "ListProvisionedConcurrencyConfigs",
765 segs.get(2).map(|s| s.to_string()),
766 ));
767 }
768
769 if segs.get(1).map(|s| s.as_str()) == Some("functions")
771 && segs.get(3).map(|s| s.as_str()) == Some("event-invoke-config")
772 {
773 let res = segs.get(2).map(|s| s.to_string());
774 return match req.method {
775 Method::POST => Some(("PutFunctionEventInvokeConfig", res)),
776 Method::PUT => Some(("UpdateFunctionEventInvokeConfig", res)),
777 Method::GET => Some(("GetFunctionEventInvokeConfig", res)),
778 Method::DELETE => Some(("DeleteFunctionEventInvokeConfig", res)),
779 _ => None,
780 };
781 }
782 if segs.get(1).map(|s| s.as_str()) == Some("functions")
783 && (segs.get(3).map(|s| s.as_str()) == Some("event-invoke-config-list")
784 || (segs.get(3).map(|s| s.as_str()) == Some("event-invoke-config")
785 && segs.get(4).map(|s| s.as_str()) == Some("list")))
786 && req.method == Method::GET
787 {
788 return Some((
789 "ListFunctionEventInvokeConfigs",
790 segs.get(2).map(|s| s.to_string()),
791 ));
792 }
793
794 if segs.get(1).map(|s| s.as_str()) == Some("functions")
796 && segs.get(3).map(|s| s.as_str()) == Some("recursion-config")
797 {
798 let res = segs.get(2).map(|s| s.to_string());
799 return match req.method {
800 Method::PUT => Some(("PutFunctionRecursionConfig", res)),
801 Method::GET => Some(("GetFunctionRecursionConfig", res)),
802 _ => None,
803 };
804 }
805
806 if segs.get(1).map(|s| s.as_str()) == Some("functions")
808 && segs.get(3).map(|s| s.as_str()) == Some("runtime-management-config")
809 {
810 let res = segs.get(2).map(|s| s.to_string());
811 return match req.method {
812 Method::PUT => Some(("PutRuntimeManagementConfig", res)),
813 Method::GET => Some(("GetRuntimeManagementConfig", res)),
814 _ => None,
815 };
816 }
817
818 if segs.get(1).map(|s| s.as_str()) == Some("functions")
820 && segs.get(3).map(|s| s.as_str()) == Some("code-signing-config")
821 {
822 let res = segs.get(2).map(|s| s.to_string());
823 return match req.method {
824 Method::PUT => Some(("PutFunctionCodeSigningConfig", res)),
825 Method::GET => Some(("GetFunctionCodeSigningConfig", res)),
826 Method::DELETE => Some(("DeleteFunctionCodeSigningConfig", res)),
827 _ => None,
828 };
829 }
830 if segs.get(1).map(|s| s.as_str()) == Some("code-signing-configs") {
831 let res = segs.get(2).map(|s| s.to_string());
832 return match (
833 req.method.clone(),
834 segs.len(),
835 segs.get(3).map(|s| s.as_str()),
836 ) {
837 (Method::POST, 2, _) => Some(("CreateCodeSigningConfig", None)),
838 (Method::GET, 2, _) => Some(("ListCodeSigningConfigs", None)),
839 (Method::GET, 3, _) => Some(("GetCodeSigningConfig", res)),
840 (Method::PUT, 3, _) => Some(("UpdateCodeSigningConfig", res)),
841 (Method::DELETE, 3, _) => Some(("DeleteCodeSigningConfig", res)),
842 (Method::GET, 4, Some("functions")) => {
843 Some(("ListFunctionsByCodeSigningConfig", res))
844 }
845 _ => None,
846 };
847 }
848
849 if segs.get(1).map(|s| s.as_str()) == Some("tags") && segs.len() >= 3 {
851 let res = segs[2..].join("/");
852 return match req.method {
853 Method::POST => Some(("TagResource", Some(res))),
854 Method::DELETE => Some(("UntagResource", Some(res))),
855 Method::GET => Some(("ListTags", Some(res))),
856 _ => None,
857 };
858 }
859
860 if segs.get(1).map(|s| s.as_str()) == Some("functions")
862 && segs.get(3).map(|s| s.as_str()) == Some("url")
863 {
864 let res = segs.get(2).map(|s| s.to_string());
865 return match req.method {
866 Method::POST => Some(("CreateFunctionUrlConfig", res)),
867 Method::GET => Some(("GetFunctionUrlConfig", res)),
868 Method::PUT => Some(("UpdateFunctionUrlConfig", res)),
869 Method::DELETE => Some(("DeleteFunctionUrlConfig", res)),
870 _ => None,
871 };
872 }
873 if segs.get(1).map(|s| s.as_str()) == Some("function-urls") && req.method == Method::GET {
874 return Some(("ListFunctionUrlConfigs", None));
875 }
876 if segs.get(1).map(|s| s.as_str()) == Some("functions")
877 && segs.get(3).map(|s| s.as_str()) == Some("urls")
878 && req.method == Method::GET
879 {
880 return Some(("ListFunctionUrlConfigs", segs.get(2).map(|s| s.to_string())));
881 }
882 if segs.get(1).map(|s| s.as_str()) == Some("functions")
887 && segs.get(3).map(|s| s.as_str()) == Some("function-scaling-config")
888 {
889 let res = segs.get(2).map(|s| s.to_string());
890 return match req.method {
891 Method::PUT => Some(("PutFunctionScalingConfig", res)),
892 Method::GET => Some(("GetFunctionScalingConfig", res)),
893 _ => None,
894 };
895 }
896
897 if prefix == "2018-10-31" && segs.get(1).map(|s| s.as_str()) == Some("layers") {
904 let layer = segs.get(2).map(|s| s.to_string());
905 let third = segs.get(3).map(|s| s.as_str());
906 let version = segs.get(4).map(|s| s.to_string());
907 return match (&req.method, segs.len(), third, version.is_some()) {
908 (&Method::GET, 2, _, _) => Some(("ListLayers", None)),
909 (&Method::POST, 4, Some("versions"), false) => Some(("PublishLayerVersion", layer)),
910 (&Method::GET, 4, Some("versions"), false) => {
911 if req.raw_path.ends_with("/versions/") {
916 Some(("GetLayerVersion", layer))
917 } else {
918 Some(("ListLayerVersions", layer))
919 }
920 }
921 (&Method::GET, 5, Some("versions"), true) => Some(("GetLayerVersion", version)),
922 (&Method::DELETE, 5, Some("versions"), true) => {
923 Some(("DeleteLayerVersion", version))
924 }
925 (&Method::DELETE, 4, Some("versions"), false)
929 if req.raw_path.ends_with("/versions/") =>
930 {
931 Some(("DeleteLayerVersion", layer))
932 }
933 (&Method::GET, 6, Some("versions"), true)
934 if segs.get(5).map(|s| s.as_str()) == Some("policy") =>
935 {
936 Some(("GetLayerVersionPolicy", version))
937 }
938 (&Method::POST, 6, Some("versions"), true)
939 if segs.get(5).map(|s| s.as_str()) == Some("policy") =>
940 {
941 Some(("AddLayerVersionPermission", version))
942 }
943 (&Method::DELETE, 7, Some("versions"), true)
944 if segs.get(5).map(|s| s.as_str()) == Some("policy") =>
945 {
946 Some(("RemoveLayerVersionPermission", version))
947 }
948 _ => None,
949 };
950 }
951
952 if prefix == "2018-10-31"
954 && segs.get(1).map(|s| s.as_str()) == Some("layers-by-arn")
955 && req.method == Method::GET
956 {
957 return Some(("GetLayerVersionByArn", None));
958 }
959
960 if prefix != "2015-03-31" {
964 return None;
965 }
966
967 let collection = segs.get(1).map(|s| s.as_str());
968 let resource = segs.get(2).map(|s| s.to_string());
969 let third = segs.get(3).map(|s| s.as_str());
970 let fourth = segs.get(4).map(|s| s.as_str());
971
972 let action = match (&req.method, segs.len(), collection, third) {
984 (&Method::POST, 2, Some("functions"), _) => "CreateFunction",
985 (&Method::GET, 2, Some("functions"), _) => {
986 if req.raw_path.ends_with("/functions/") {
991 "GetFunction"
992 } else {
993 "ListFunctions"
994 }
995 }
996 (&Method::GET, 3, Some("functions"), _) => "GetFunction",
997 (&Method::DELETE, 3, Some("functions"), _) => "DeleteFunction",
998 (&Method::POST, 4, Some("functions"), Some("invocations")) => "Invoke",
999 (&Method::POST, 4, Some("functions"), Some("invoke-async")) => "InvokeAsync",
1000 (&Method::POST, 4, Some("functions"), Some("response-streaming-invocations")) => {
1001 "InvokeWithResponseStream"
1002 }
1003 (&Method::POST, 4, Some("functions"), Some("versions")) => "PublishVersion",
1004 (&Method::GET, 4, Some("functions"), Some("versions")) => "ListVersionsByFunction",
1005 (&Method::POST, 4, Some("functions"), Some("policy")) => "AddPermission",
1006 (&Method::GET, 4, Some("functions"), Some("policy")) => "GetPolicy",
1007 (&Method::DELETE, 5, Some("functions"), Some("policy")) => "RemovePermission",
1008 (&Method::POST, 4, Some("functions"), Some("aliases")) => "CreateAlias",
1009 (&Method::GET, 4, Some("functions"), Some("aliases")) => {
1010 if req.raw_path.ends_with("/aliases/") {
1015 "GetAlias"
1016 } else {
1017 "ListAliases"
1018 }
1019 }
1020 (&Method::GET, 5, Some("functions"), Some("aliases")) => "GetAlias",
1021 (&Method::PUT, 5, Some("functions"), Some("aliases")) => "UpdateAlias",
1022 (&Method::DELETE, 5, Some("functions"), Some("aliases")) => "DeleteAlias",
1023 (&Method::GET, 4, Some("functions"), Some("configuration")) => {
1024 "GetFunctionConfiguration"
1025 }
1026 (&Method::PUT, 4, Some("functions"), Some("configuration")) => {
1027 "UpdateFunctionConfiguration"
1028 }
1029 (&Method::PUT, 4, Some("functions"), Some("code")) => "UpdateFunctionCode",
1030 (&Method::PUT, 4, Some("functions"), Some("concurrency")) => "PutFunctionConcurrency",
1031 (&Method::GET, 4, Some("functions"), Some("concurrency")) => "GetFunctionConcurrency",
1032 (&Method::DELETE, 4, Some("functions"), Some("concurrency")) => {
1033 "DeleteFunctionConcurrency"
1034 }
1035 (&Method::PUT, 4, Some("functions"), Some("provisioned-concurrency")) => {
1036 "PutProvisionedConcurrencyConfig"
1037 }
1038 (&Method::GET, 4, Some("functions"), Some("provisioned-concurrency")) => {
1039 "GetProvisionedConcurrencyConfig"
1040 }
1041 (&Method::DELETE, 4, Some("functions"), Some("provisioned-concurrency")) => {
1042 "DeleteProvisionedConcurrencyConfig"
1043 }
1044 (&Method::GET, 4, Some("functions"), Some("provisioned-concurrency-configs")) => {
1045 "ListProvisionedConcurrencyConfigs"
1046 }
1047 (&Method::PUT, 4, Some("functions"), Some("event-invoke-config")) => {
1048 "UpdateFunctionEventInvokeConfig"
1049 }
1050 (&Method::POST, 4, Some("functions"), Some("event-invoke-config")) => {
1051 "PutFunctionEventInvokeConfig"
1052 }
1053 (&Method::GET, 4, Some("functions"), Some("event-invoke-config")) => {
1054 "GetFunctionEventInvokeConfig"
1055 }
1056 (&Method::DELETE, 4, Some("functions"), Some("event-invoke-config")) => {
1057 "DeleteFunctionEventInvokeConfig"
1058 }
1059 (&Method::GET, 4, Some("functions"), Some("event-invoke-config-list")) => {
1060 "ListFunctionEventInvokeConfigs"
1061 }
1062 (&Method::PUT, 4, Some("functions"), Some("code-signing-config")) => {
1063 "PutFunctionCodeSigningConfig"
1064 }
1065 (&Method::GET, 4, Some("functions"), Some("code-signing-config")) => {
1066 "GetFunctionCodeSigningConfig"
1067 }
1068 (&Method::DELETE, 4, Some("functions"), Some("code-signing-config")) => {
1069 "DeleteFunctionCodeSigningConfig"
1070 }
1071 (&Method::PUT, 4, Some("functions"), Some("runtime-management-config")) => {
1072 "PutRuntimeManagementConfig"
1073 }
1074 (&Method::GET, 4, Some("functions"), Some("runtime-management-config")) => {
1075 "GetRuntimeManagementConfig"
1076 }
1077 (&Method::PUT, 4, Some("functions"), Some("function-scaling-config")) => {
1078 "PutFunctionScalingConfig"
1079 }
1080 (&Method::GET, 4, Some("functions"), Some("function-scaling-config")) => {
1081 "GetFunctionScalingConfig"
1082 }
1083 (&Method::PUT, 4, Some("functions"), Some("recursion-config")) => {
1084 "PutFunctionRecursionConfig"
1085 }
1086 (&Method::GET, 4, Some("functions"), Some("recursion-config")) => {
1087 "GetFunctionRecursionConfig"
1088 }
1089 (&Method::POST, 2, Some("event-source-mappings"), _) => "CreateEventSourceMapping",
1090 (&Method::GET, 2, Some("event-source-mappings"), _) => "ListEventSourceMappings",
1091 (&Method::GET, 3, Some("event-source-mappings"), _) => "GetEventSourceMapping",
1092 (&Method::PUT, 3, Some("event-source-mappings"), _) => "UpdateEventSourceMapping",
1093 (&Method::DELETE, 3, Some("event-source-mappings"), _) => "DeleteEventSourceMapping",
1094 (&Method::POST, 3, Some("tags"), _) => "TagResource",
1095 (&Method::DELETE, 3, Some("tags"), _) => "UntagResource",
1096 (&Method::GET, 3, Some("tags"), _) => "ListTags",
1097 _ => return None,
1098 };
1099 let _ = fourth;
1100
1101 Some((action, resource))
1102 }
1103
1104 fn create_function(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1105 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1106 let input = CreateFunctionInput::from_body(&body)?;
1107
1108 let raw = input.function_name.as_str();
1115 if raw.is_empty() || raw.chars().count() > 140 {
1116 return Err(AwsServiceError::aws_error(
1117 StatusCode::BAD_REQUEST,
1118 "InvalidParameterValueException",
1119 format!(
1120 "1 validation error detected: Value '{}' at 'functionName' failed to \
1121 satisfy constraint: Member must have length less than or equal to 140",
1122 raw
1123 ),
1124 ));
1125 }
1126
1127 if let Some(ref validator) = self.role_trust_validator {
1132 if let Err(err) =
1133 validator.validate(&req.account_id, &input.role, "lambda.amazonaws.com")
1134 {
1135 return Err(AwsServiceError::aws_error(
1136 StatusCode::BAD_REQUEST,
1137 "InvalidParameterValueException",
1138 err.to_string(),
1139 ));
1140 }
1141 }
1142
1143 let mut accounts = self.state.write();
1144 let layer_attachments =
1147 crate::extras::resolve_layer_attachments(&accounts, input.layer_arns.clone());
1148 let state = accounts.get_or_create(&req.account_id);
1149
1150 if state.functions.contains_key(&input.function_name) {
1151 return Err(AwsServiceError::aws_error(
1152 StatusCode::CONFLICT,
1153 "ResourceConflictException",
1154 format!("Function already exist: {}", input.function_name),
1155 ));
1156 }
1157
1158 let code_bytes = input.code_zip.as_deref().unwrap_or(&input.code_fallback);
1161 let mut hasher = Sha256::new();
1162 hasher.update(code_bytes);
1163 let hash = hasher.finalize();
1164 let code_sha256 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, hash);
1165 let code_size = code_bytes.len() as i64;
1166
1167 let function_arn = format!(
1168 "arn:aws:lambda:{}:{}:function:{}",
1169 state.region, state.account_id, input.function_name
1170 );
1171 let now = Utc::now();
1172
1173 let func = LambdaFunction {
1174 function_name: input.function_name.clone(),
1175 function_arn,
1176 runtime: input.runtime,
1177 role: input.role,
1178 handler: input.handler,
1179 description: input.description,
1180 timeout: input.timeout,
1181 memory_size: input.memory_size,
1182 code_sha256,
1183 code_size,
1184 version: "$LATEST".to_string(),
1185 last_modified: now,
1186 tags: input.tags,
1187 environment: input.environment,
1188 architectures: input.architectures,
1189 package_type: input.package_type,
1190 code_zip: input.code_zip,
1191 image_uri: input.image_uri,
1192 policy: None,
1193 layers: layer_attachments,
1194 revision_id: uuid::Uuid::new_v4().to_string(),
1195 tracing_mode: input.tracing_mode,
1196 kms_key_arn: input.kms_key_arn,
1197 ephemeral_storage_size: input.ephemeral_storage_size,
1198 vpc_config: input.vpc_config,
1199 snap_start: input.snap_start,
1200 dead_letter_config_arn: input.dead_letter_config_arn,
1201 file_system_configs: input.file_system_configs,
1202 logging_config: input.logging_config,
1203 image_config: input.image_config,
1204 durable_config: input.durable_config,
1205 signing_profile_version_arn: None,
1206 signing_job_arn: None,
1207 runtime_version_config: None,
1208 master_arn: None,
1209 state_reason: None,
1210 state_reason_code: None,
1211 last_update_status_reason: None,
1212 last_update_status_reason_code: None,
1213 };
1214
1215 let response = self.function_config_json(&func);
1216
1217 state.functions.insert(input.function_name, func);
1218
1219 Ok(AwsResponse::json(StatusCode::CREATED, response.to_string()))
1220 }
1221
1222 fn get_function(
1223 &self,
1224 req: &AwsRequest,
1225 function_name: &str,
1226 account_id: &str,
1227 region: &str,
1228 qualifier: Option<&str>,
1229 ) -> Result<AwsResponse, AwsServiceError> {
1230 if function_name.is_empty() {
1231 return Err(AwsServiceError::aws_error(
1232 StatusCode::BAD_REQUEST,
1233 "InvalidParameterValueException",
1234 "FunctionName is required",
1235 ));
1236 }
1237 let accounts = self.state.read();
1238 let empty = LambdaState::new(account_id, region);
1239 let state = accounts.get(account_id).unwrap_or(&empty);
1240 let live = state.functions.get(function_name).ok_or_else(|| {
1241 AwsServiceError::aws_error(
1242 StatusCode::NOT_FOUND,
1243 "ResourceNotFoundException",
1244 format!(
1245 "Function not found: arn:aws:lambda:{}:{}:function:{}",
1246 state.region, state.account_id, function_name
1247 ),
1248 )
1249 })?;
1250
1251 let resolved_version = resolve_qualifier_to_version(state, function_name, qualifier);
1256 let (func, version_label) = match resolved_version {
1257 None => (live, "$LATEST".to_string()),
1258 Some(v) => {
1259 let snap = state
1260 .function_version_snapshots
1261 .get(function_name)
1262 .and_then(|m| m.get(&v))
1263 .ok_or_else(|| {
1264 AwsServiceError::aws_error(
1265 StatusCode::NOT_FOUND,
1266 "ResourceNotFoundException",
1267 format!(
1268 "Function not found: arn:aws:lambda:{}:{}:function:{}:{v}",
1269 state.region, state.account_id, function_name
1270 ),
1271 )
1272 })?;
1273 (snap, v)
1274 }
1275 };
1276
1277 let mut config = self.function_config_json(func);
1278 config["Version"] = json!(version_label);
1279 if version_label != "$LATEST" {
1280 config["FunctionArn"] = json!(format!("{}:{version_label}", live.function_arn));
1281 config["MasterArn"] = json!(live.function_arn);
1282 }
1283 let code = if let Some(ref uri) = func.image_uri {
1284 json!({
1285 "ImageUri": uri,
1286 "ResolvedImageUri": uri,
1287 "RepositoryType": "ECR",
1288 })
1289 } else {
1290 json!({
1294 "Location": crate::extras::function_code_url(
1295 req,
1296 &state.account_id,
1297 function_name,
1298 &version_label,
1299 ),
1300 "RepositoryType": "S3",
1301 })
1302 };
1303 let response = json!({
1304 "Code": code,
1305 "Configuration": config,
1306 "Tags": live.tags,
1307 });
1308
1309 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1310 }
1311
1312 fn delete_function(
1313 &self,
1314 function_name: &str,
1315 account_id: &str,
1316 qualifier: Option<&str>,
1317 ) -> Result<AwsResponse, AwsServiceError> {
1318 let mut accounts = self.state.write();
1319 let state = accounts.get_or_create(account_id);
1320 let region = state.region.clone();
1321 let account_id_owned = state.account_id.clone();
1322
1323 if let Some(q) = qualifier {
1329 if q == "$LATEST" {
1330 return Err(AwsServiceError::aws_error(
1331 StatusCode::BAD_REQUEST,
1332 "InvalidParameterValueException",
1333 "$LATEST version cannot be deleted without deleting the function.",
1334 ));
1335 }
1336 if !q.chars().all(|c| c.is_ascii_digit()) {
1337 return Err(AwsServiceError::aws_error(
1338 StatusCode::BAD_REQUEST,
1339 "InvalidParameterValueException",
1340 format!(
1341 "Value '{q}' at 'qualifier' failed to satisfy constraint: Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)"
1342 ),
1343 ));
1344 }
1345 if !state.functions.contains_key(function_name) {
1347 return Err(AwsServiceError::aws_error(
1348 StatusCode::NOT_FOUND,
1349 "ResourceNotFoundException",
1350 format!(
1351 "Function not found: arn:aws:lambda:{region}:{account_id_owned}:function:{function_name}:{q}"
1352 ),
1353 ));
1354 }
1355 let snap_existed = state
1356 .function_version_snapshots
1357 .get_mut(function_name)
1358 .map(|m| m.remove(q).is_some())
1359 .unwrap_or(false);
1360 if !snap_existed {
1361 return Err(AwsServiceError::aws_error(
1362 StatusCode::NOT_FOUND,
1363 "ResourceNotFoundException",
1364 format!(
1365 "Function not found: arn:aws:lambda:{region}:{account_id_owned}:function:{function_name}:{q}"
1366 ),
1367 ));
1368 }
1369 if let Some(list) = state.function_versions.get_mut(function_name) {
1372 list.retain(|v| v != q);
1373 }
1374 return Ok(AwsResponse::json(StatusCode::NO_CONTENT, ""));
1375 }
1376
1377 if state.functions.remove(function_name).is_none() {
1378 return Err(AwsServiceError::aws_error(
1379 StatusCode::NOT_FOUND,
1380 "ResourceNotFoundException",
1381 format!(
1382 "Function not found: arn:aws:lambda:{region}:{account_id_owned}:function:{function_name}"
1383 ),
1384 ));
1385 }
1386 state.function_versions.remove(function_name);
1390 state.function_version_snapshots.remove(function_name);
1391 let prefix = format!("{function_name}:");
1393 state.aliases.retain(|k, _| !k.starts_with(&prefix));
1394
1395 if let Some(ref runtime) = self.runtime {
1397 let rt = runtime.clone();
1398 let name = function_name.to_string();
1399 tokio::spawn(async move { rt.stop_container(&name).await });
1400 }
1401
1402 Ok(AwsResponse::json(StatusCode::NO_CONTENT, ""))
1403 }
1404
1405 fn list_functions(
1406 &self,
1407 account_id: &str,
1408 function_version: Option<&str>,
1409 ) -> Result<AwsResponse, AwsServiceError> {
1410 if let Some(fv) = function_version {
1413 if fv != "ALL" {
1414 return Err(AwsServiceError::aws_error(
1415 StatusCode::BAD_REQUEST,
1416 "InvalidParameterValueException",
1417 format!("Invalid FunctionVersion value '{}'; expected 'ALL'", fv),
1418 ));
1419 }
1420 }
1421 let accounts = self.state.read();
1422 let empty = LambdaState::new(account_id, "");
1423 let state = accounts.get(account_id).unwrap_or(&empty);
1424 let functions: Vec<Value> = state
1425 .functions
1426 .values()
1427 .map(|f| self.function_config_json(f))
1428 .collect();
1429
1430 let response = json!({
1436 "Functions": functions,
1437 "NextMarker": "",
1438 });
1439
1440 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1441 }
1442
1443 async fn invoke(
1444 &self,
1445 function_name: &str,
1446 payload: &[u8],
1447 account_id: &str,
1448 invocation_type: InvocationType,
1449 qualifier: Option<&str>,
1450 ) -> Result<AwsResponse, AwsServiceError> {
1451 let resolved_version: Option<String> = {
1462 let accounts = self.state.read();
1463 let empty = LambdaState::new(account_id, "");
1464 let state = accounts.get(account_id).unwrap_or(&empty);
1465 resolve_qualifier_to_version(state, function_name, qualifier)
1466 };
1467 let executed_version = resolved_version
1468 .clone()
1469 .unwrap_or_else(|| "$LATEST".to_string());
1470 let (func, layer_zips) = {
1471 let accounts = self.state.read();
1472 let empty = LambdaState::new(account_id, "");
1473 let state = accounts.get(account_id).unwrap_or(&empty);
1474 let func = match resolved_version.as_deref() {
1480 Some(v) => state
1481 .function_version_snapshots
1482 .get(function_name)
1483 .and_then(|m| m.get(v))
1484 .cloned()
1485 .or_else(|| state.functions.get(function_name).cloned()),
1486 None => state.functions.get(function_name).cloned(),
1487 }
1488 .ok_or_else(|| {
1489 AwsServiceError::aws_error(
1490 StatusCode::NOT_FOUND,
1491 "ResourceNotFoundException",
1492 format!(
1493 "Function not found: arn:aws:lambda:{}:{}:function:{}",
1494 state.region, state.account_id, function_name
1495 ),
1496 )
1497 })?;
1498 let mut layer_zips: Vec<Vec<u8>> = Vec::with_capacity(func.layers.len());
1503 for attached in &func.layers {
1504 let bytes = crate::extras::parse_layer_version_arn(&attached.arn).and_then(
1505 |(acct, name, ver)| {
1506 accounts
1507 .get(&acct)
1508 .and_then(|s| s.layers.get(&name))
1509 .and_then(|l| l.versions.iter().find(|v| v.version == ver))
1510 .and_then(|v| v.code_zip.clone())
1511 },
1512 );
1513 match bytes {
1514 Some(b) => layer_zips.push(b),
1515 None => tracing::warn!(
1516 function = %function_name,
1517 layer_arn = %attached.arn,
1518 "attached layer not resolvable; skipping /opt mount for this layer"
1519 ),
1520 }
1521 }
1522 (func, layer_zips)
1523 };
1524
1525 let concurrency_key = format!("{account_id}:{function_name}");
1530 let _concurrency_guard = {
1531 let cap = {
1532 let accounts = self.state.read();
1533 accounts
1534 .get(account_id)
1535 .and_then(|s| s.function_concurrency.get(function_name).copied())
1536 };
1537 let mut map = self.inflight_invocations.write();
1538 let current = map.get(&concurrency_key).copied().unwrap_or(0);
1539 if let Some(limit) = cap {
1540 if current >= limit {
1541 return Err(AwsServiceError::aws_error_with_fields(
1545 StatusCode::TOO_MANY_REQUESTS,
1546 "TooManyRequestsException",
1547 "Rate Exceeded.",
1548 vec![(
1549 "Reason".to_string(),
1550 "ReservedFunctionConcurrentInvocationLimitExceeded".to_string(),
1551 )],
1552 ));
1553 }
1554 }
1555 map.insert(concurrency_key.clone(), current + 1);
1556 ConcurrencyGuard {
1557 map: self.inflight_invocations.clone(),
1558 key: concurrency_key.clone(),
1559 }
1560 };
1561
1562 if func.code_zip.is_none() {
1563 return Err(AwsServiceError::aws_error(
1564 StatusCode::BAD_REQUEST,
1565 "InvalidParameterValueException",
1566 "Function has no deployment package",
1567 ));
1568 }
1569
1570 let invoke_start = std::time::Instant::now();
1571 let dry_run_response = if matches!(invocation_type, InvocationType::DryRun) {
1572 let mut resp = AwsResponse::json(StatusCode::NO_CONTENT, "");
1573 if let Ok(v) = http::header::HeaderValue::from_str(&executed_version) {
1574 resp.headers.insert(
1575 http::header::HeaderName::from_static("x-amz-executed-version"),
1576 v,
1577 );
1578 }
1579 Some(resp)
1580 } else {
1581 None
1582 };
1583
1584 let runtime_for_invoke = if dry_run_response.is_some() {
1585 None
1586 } else {
1587 self.runtime.clone()
1588 };
1589
1590 let result: Result<AwsResponse, AwsServiceError> = if let Some(resp) = dry_run_response {
1591 Ok(resp)
1592 } else if let Some(runtime) = runtime_for_invoke {
1593 match invocation_type {
1594 InvocationType::Event => {
1595 let runtime = runtime.clone();
1600 let func_clone = func.clone();
1601 let payload_vec = payload.to_vec();
1602 let bus = self.delivery_bus.clone();
1603 let destination_config = self.lookup_destination_config(&func, account_id);
1604 let function_arn = func.function_arn.clone();
1605 let layer_zips_async = layer_zips.clone();
1606 let async_guard = _concurrency_guard;
1607 tokio::spawn(async move {
1608 let _g = async_guard;
1609 let result = match runtime
1610 .invoke(&func_clone, &payload_vec, &layer_zips_async)
1611 .await
1612 {
1613 Ok(bytes) => {
1614 let parsed: Option<serde_json::Value> =
1618 serde_json::from_slice(&bytes).ok();
1619 let is_error = parsed
1620 .as_ref()
1621 .and_then(|v| v.as_object())
1622 .map(|m| {
1623 m.contains_key("errorMessage")
1624 || m.contains_key("errorType")
1625 })
1626 .unwrap_or(false);
1627 if is_error {
1628 let msg = parsed
1629 .as_ref()
1630 .and_then(|v| v.get("errorMessage"))
1631 .and_then(|v| v.as_str())
1632 .unwrap_or("function error")
1633 .to_string();
1634 Err(msg)
1635 } else {
1636 Ok(bytes)
1637 }
1638 }
1639 Err(e) => Err(e.to_string()),
1640 };
1641 if let Some(bus) = bus {
1642 route_to_destination(
1643 bus,
1644 &function_arn,
1645 &payload_vec,
1646 &result,
1647 destination_config.as_ref(),
1648 );
1649 }
1650 });
1651 let mut resp = AwsResponse::json(StatusCode::ACCEPTED, "");
1652 if let Ok(v) = http::header::HeaderValue::from_str(&executed_version) {
1653 resp.headers.insert(
1654 http::header::HeaderName::from_static("x-amz-executed-version"),
1655 v,
1656 );
1657 }
1658 Ok(resp)
1659 }
1660 InvocationType::RequestResponse | InvocationType::DryRun => {
1661 match runtime.invoke(&func, payload, &layer_zips).await {
1662 Ok(response_bytes) => {
1663 let mut resp = AwsResponse::json(StatusCode::OK, response_bytes);
1664 if let Ok(v) = http::header::HeaderValue::from_str(&executed_version) {
1665 resp.headers.insert(
1666 http::header::HeaderName::from_static("x-amz-executed-version"),
1667 v,
1668 );
1669 }
1670 Ok(resp)
1671 }
1672 Err(e) => {
1673 tracing::error!(function = %function_name, error = %e, "Lambda invocation failed");
1674 Err(AwsServiceError::aws_error(
1675 StatusCode::INTERNAL_SERVER_ERROR,
1676 "ServiceException",
1677 format!("Lambda execution failed: {e}"),
1678 ))
1679 }
1680 }
1681 }
1682 }
1683 } else {
1684 Err(AwsServiceError::aws_error(
1685 StatusCode::INTERNAL_SERVER_ERROR,
1686 "ServiceException",
1687 "Docker/Podman is required for Lambda execution but is not available",
1688 ))
1689 };
1690
1691 if let Some(bus) = &self.delivery_bus {
1696 let dims: std::collections::BTreeMap<String, String> =
1697 [("FunctionName".to_string(), function_name.to_string())]
1698 .into_iter()
1699 .collect();
1700 let now_ms = chrono::Utc::now().timestamp_millis();
1701 let region = {
1702 let accounts = self.state.read();
1703 let empty = LambdaState::new(account_id, "");
1704 accounts
1705 .get(account_id)
1706 .map(|s| s.region.clone())
1707 .unwrap_or_else(|| empty.region)
1708 };
1709 bus.put_cloudwatch_metric(
1710 account_id,
1711 ®ion,
1712 "AWS/Lambda",
1713 "Invocations",
1714 1.0,
1715 Some("Count"),
1716 dims.clone(),
1717 now_ms,
1718 );
1719 bus.put_cloudwatch_metric(
1720 account_id,
1721 ®ion,
1722 "AWS/Lambda",
1723 "Duration",
1724 invoke_start.elapsed().as_millis() as f64,
1725 Some("Milliseconds"),
1726 dims.clone(),
1727 now_ms,
1728 );
1729 if result.is_err() {
1730 bus.put_cloudwatch_metric(
1731 account_id,
1732 ®ion,
1733 "AWS/Lambda",
1734 "Errors",
1735 1.0,
1736 Some("Count"),
1737 dims,
1738 now_ms,
1739 );
1740 }
1741 }
1742
1743 result
1744 }
1745
1746 fn lookup_destination_config(
1751 &self,
1752 func: &crate::state::LambdaFunction,
1753 account_id: &str,
1754 ) -> Option<serde_json::Value> {
1755 let accounts = self.state.read();
1756 let state = accounts.get(account_id)?;
1757 let key = format!("{}:$LATEST", func.function_name);
1758 state
1759 .event_invoke_configs
1760 .get(&key)
1761 .and_then(|cfg| cfg.destination_config.clone())
1762 .filter(|v| !v.is_null() && !v.as_object().map(|o| o.is_empty()).unwrap_or(false))
1763 }
1764
1765 pub(crate) fn publish_version(
1766 &self,
1767 function_name: &str,
1768 account_id: &str,
1769 req: &AwsRequest,
1770 ) -> Result<AwsResponse, AwsServiceError> {
1771 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1775 let supplied_revision = body["RevisionId"].as_str().map(String::from);
1776 let supplied_sha = body["CodeSha256"].as_str().map(String::from);
1777 let description_override = body["Description"].as_str().map(String::from);
1778
1779 let mut accounts = self.state.write();
1780 let state = accounts.get_or_create(account_id);
1781 let func = state.functions.get(function_name).ok_or_else(|| {
1782 AwsServiceError::aws_error(
1783 StatusCode::NOT_FOUND,
1784 "ResourceNotFoundException",
1785 format!(
1786 "Function not found: arn:aws:lambda:{}:{}:function:{}",
1787 state.region, state.account_id, function_name
1788 ),
1789 )
1790 })?;
1791
1792 if let Some(ref rev) = supplied_revision {
1793 if rev != &func.revision_id {
1794 return Err(AwsServiceError::aws_error(
1795 StatusCode::PRECONDITION_FAILED,
1796 "PreconditionFailedException",
1797 "The RevisionId provided does not match the latest RevisionId for the Lambda function. Call the GetFunction or the GetAlias API to retrieve the latest RevisionId for your resource.",
1798 ));
1799 }
1800 }
1801 if let Some(ref sha) = supplied_sha {
1802 if sha != &func.code_sha256 {
1803 return Err(AwsServiceError::aws_error(
1804 StatusCode::PRECONDITION_FAILED,
1805 "PreconditionFailedException",
1806 "CodeSha256 does not match the SHA-256 of the function's deployment package.",
1807 ));
1808 }
1809 }
1810
1811 let existing = state
1815 .function_versions
1816 .get(function_name)
1817 .cloned()
1818 .unwrap_or_default();
1819 let latest_version = existing.iter().filter_map(|v| v.parse::<u64>().ok()).max();
1820
1821 if let Some(latest_num) = latest_version {
1834 let latest_str = latest_num.to_string();
1835 if let Some(prev_snap) = state
1836 .function_version_snapshots
1837 .get(function_name)
1838 .and_then(|m| m.get(&latest_str))
1839 .cloned()
1840 {
1841 let effective_desc = description_override
1842 .clone()
1843 .unwrap_or_else(|| func.description.clone());
1844 if function_config_unchanged_for_publish(&prev_snap, func, &effective_desc) {
1845 let mut config = self.function_config_json(&prev_snap);
1846 config["Version"] = json!(latest_str);
1847 config["FunctionArn"] = json!(format!("{}:{latest_str}", func.function_arn));
1848 config["MasterArn"] = json!(func.function_arn);
1849 return Ok(AwsResponse::json(StatusCode::CREATED, config.to_string()));
1850 }
1851 }
1852 }
1853
1854 let next: u64 = latest_version.unwrap_or(0) + 1;
1855 let next_str = next.to_string();
1856
1857 let mut snapshot = func.clone();
1859 snapshot.version = next_str.clone();
1860 snapshot.master_arn = Some(func.function_arn.clone());
1861 if let Some(desc) = description_override {
1862 snapshot.description = desc;
1863 }
1864 snapshot.revision_id = uuid::Uuid::new_v4().to_string();
1866
1867 if let Some(snap) = snapshot.snap_start.as_mut() {
1873 if snap.get("ApplyOn").and_then(|v| v.as_str()) == Some("PublishedVersions") {
1874 snap["OptimizationStatus"] = json!("On");
1875 }
1876 }
1877
1878 state
1880 .function_versions
1881 .entry(function_name.to_string())
1882 .or_default()
1883 .push(next_str.clone());
1884 state
1885 .function_version_snapshots
1886 .entry(function_name.to_string())
1887 .or_default()
1888 .insert(next_str.clone(), snapshot.clone());
1889
1890 let mut config = self.function_config_json(&snapshot);
1891 config["Version"] = json!(next_str);
1892 config["FunctionArn"] = json!(format!("{}:{next_str}", func.function_arn));
1893 config["MasterArn"] = json!(func.function_arn);
1894
1895 Ok(AwsResponse::json(StatusCode::CREATED, config.to_string()))
1896 }
1897
1898 pub(crate) fn function_config_json(&self, func: &LambdaFunction) -> Value {
1899 let env_vars = if func.environment.is_empty() {
1901 json!({ "Variables": {} })
1902 } else {
1903 json!({ "Variables": func.environment })
1904 };
1905
1906 let tracing_mode = func.tracing_mode.as_deref().unwrap_or("PassThrough");
1907 let ephemeral_size = func.ephemeral_storage_size.unwrap_or(512);
1908
1909 let mut config = json!({
1910 "FunctionName": func.function_name,
1911 "FunctionArn": func.function_arn,
1912 "Runtime": func.runtime,
1913 "Role": func.role,
1914 "Handler": func.handler,
1915 "Description": func.description,
1916 "Timeout": func.timeout,
1917 "MemorySize": func.memory_size,
1918 "CodeSha256": func.code_sha256,
1919 "CodeSize": func.code_size,
1920 "Version": func.version,
1921 "LastModified": func.last_modified.format("%Y-%m-%dT%H:%M:%S%.3f+0000").to_string(),
1922 "PackageType": func.package_type,
1923 "Architectures": func.architectures,
1924 "Environment": env_vars,
1925 "State": "Active",
1926 "LastUpdateStatus": "Successful",
1927 "TracingConfig": { "Mode": tracing_mode },
1928 "RevisionId": func.revision_id,
1929 "EphemeralStorage": { "Size": ephemeral_size },
1930 "SnapStart": func.snap_start.clone().unwrap_or_else(|| json!({
1931 "ApplyOn": "None",
1932 "OptimizationStatus": "Off",
1933 })),
1934 });
1935 if let Some(ref kms) = func.kms_key_arn {
1936 config["KMSKeyArn"] = json!(kms);
1937 }
1938 if let Some(ref vpc) = func.vpc_config {
1939 config["VpcConfig"] = vpc.clone();
1940 }
1941 if let Some(ref dlq) = func.dead_letter_config_arn {
1942 config["DeadLetterConfig"] = json!({ "TargetArn": dlq });
1943 }
1944 if !func.file_system_configs.is_empty() {
1945 config["FileSystemConfigs"] = json!(func.file_system_configs);
1946 }
1947 if let Some(ref lg) = func.logging_config {
1948 config["LoggingConfig"] = lg.clone();
1949 }
1950 if let Some(ref ic) = func.image_config {
1951 config["ImageConfigResponse"] = json!({ "ImageConfig": ic });
1952 }
1953 if let Some(ref dc) = func.durable_config {
1954 config["DurableConfig"] = dc.clone();
1955 }
1956 if let Some(ref s) = func.signing_profile_version_arn {
1957 config["SigningProfileVersionArn"] = json!(s);
1958 }
1959 if let Some(ref s) = func.signing_job_arn {
1960 config["SigningJobArn"] = json!(s);
1961 }
1962 if let Some(ref rv) = func.runtime_version_config {
1963 config["RuntimeVersionConfig"] = rv.clone();
1964 }
1965 if let Some(ref m) = func.master_arn {
1966 config["MasterArn"] = json!(m);
1967 }
1968 if !func.layers.is_empty() {
1973 config["Layers"] = json!(func
1974 .layers
1975 .iter()
1976 .map(|l| json!({"Arn": l.arn, "CodeSize": l.code_size}))
1977 .collect::<Vec<_>>());
1978 }
1979 if let Some(ref r) = func.state_reason {
1980 config["StateReason"] = json!(r);
1981 }
1982 if let Some(ref c) = func.state_reason_code {
1983 config["StateReasonCode"] = json!(c);
1984 }
1985 if let Some(ref r) = func.last_update_status_reason {
1986 config["LastUpdateStatusReason"] = json!(r);
1987 }
1988 if let Some(ref c) = func.last_update_status_reason_code {
1989 config["LastUpdateStatusReasonCode"] = json!(c);
1990 }
1991 config
1992 }
1993}
1994
1995#[async_trait]
1996impl AwsService for LambdaService {
1997 fn service_name(&self) -> &str {
1998 "lambda"
1999 }
2000
2001 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2002 let (action, resource_name) = Self::resolve_action(&req).ok_or_else(|| {
2003 const KNOWN_COLLECTIONS: &[&str] = &[
2014 "functions",
2015 "layers",
2016 "layers-by-arn",
2017 "event-source-mappings",
2018 "tags",
2019 "account-settings",
2020 "code-signing-configs",
2021 ];
2022 let is_known_collection = req
2023 .path_segments
2024 .get(1)
2025 .map(|s| KNOWN_COLLECTIONS.contains(&s.as_str()))
2026 .unwrap_or(false);
2027 if is_known_collection {
2028 AwsServiceError::aws_error(
2029 StatusCode::BAD_REQUEST,
2030 "InvalidParameterValueException",
2031 format!(
2032 "Could not route request {} {} — missing or invalid identifier",
2033 req.method, req.raw_path
2034 ),
2035 )
2036 } else {
2037 AwsServiceError::aws_error(
2038 StatusCode::NOT_FOUND,
2039 "UnknownOperationException",
2040 format!("Unknown operation: {} {}", req.method, req.raw_path),
2041 )
2042 }
2043 })?;
2044
2045 let resource_name = if action_takes_function_name(action) {
2050 if let Some(raw) = resource_name.as_ref() {
2057 let len = raw.chars().count();
2058 let limit = if raw.starts_with("arn:") { 200 } else { 140 };
2069 if raw.is_empty() || len > limit {
2070 let (code, msg) = if action == "InvokeAsync" {
2071 (
2072 "ResourceNotFoundException",
2073 format!("Function not found: {}", raw),
2074 )
2075 } else {
2076 (
2077 "InvalidParameterValueException",
2078 format!(
2079 "1 validation error detected: Value '{}' at 'functionName' failed to \
2080 satisfy constraint: Member must have length less than or equal to 140",
2081 raw
2082 ),
2083 )
2084 };
2085 return Err(AwsServiceError::aws_error(
2086 if action == "InvokeAsync" {
2087 StatusCode::NOT_FOUND
2088 } else {
2089 StatusCode::BAD_REQUEST
2090 },
2091 code,
2092 msg,
2093 ));
2094 }
2095 }
2096 resource_name.map(|s| normalize_function_name(&s))
2097 } else {
2098 resource_name
2099 };
2100
2101 if let Some(raw) = req.query_params.get("MaxItems") {
2107 if let Ok(n) = raw.parse::<i64>() {
2108 let max = match action {
2109 "ListLayers"
2110 | "ListLayerVersions"
2111 | "ListFunctionUrlConfigs"
2112 | "ListProvisionedConcurrencyConfigs"
2113 | "ListFunctionEventInvokeConfigs"
2114 | "ListAliases" => 50,
2115 _ => 10000,
2116 };
2117 if !(1..=max).contains(&n) {
2118 return Err(AwsServiceError::aws_error(
2119 StatusCode::BAD_REQUEST,
2120 "InvalidParameterValueException",
2121 format!("MaxItems must be between 1 and {} (got {})", max, n),
2122 ));
2123 }
2124 }
2125 }
2126
2127 if let Some(q) = req.query_params.get("Qualifier") {
2132 let len = q.chars().count();
2133 if q.is_empty() || len > 128 {
2134 return Err(AwsServiceError::aws_error(
2135 StatusCode::BAD_REQUEST,
2136 "InvalidParameterValueException",
2137 format!("Qualifier must be 1..128 characters (got length {})", len),
2138 ));
2139 }
2140 }
2141 if let Some(fv) = req.query_params.get("FunctionVersion") {
2144 let len = fv.chars().count();
2145 if fv.is_empty() || len > 1024 {
2146 return Err(AwsServiceError::aws_error(
2147 StatusCode::BAD_REQUEST,
2148 "InvalidParameterValueException",
2149 format!(
2150 "FunctionVersion must be 1..1024 characters (got length {})",
2151 len
2152 ),
2153 ));
2154 }
2155 }
2156
2157 let mutates = matches!(
2158 action,
2159 "CreateFunction"
2160 | "DeleteFunction"
2161 | "PublishVersion"
2162 | "AddPermission"
2163 | "RemovePermission"
2164 | "CreateEventSourceMapping"
2165 | "DeleteEventSourceMapping"
2166 | "UpdateEventSourceMapping"
2167 | "UpdateFunctionCode"
2168 | "UpdateFunctionConfiguration"
2169 | "CreateAlias"
2170 | "DeleteAlias"
2171 | "UpdateAlias"
2172 | "PublishLayerVersion"
2173 | "DeleteLayerVersion"
2174 | "AddLayerVersionPermission"
2175 | "RemoveLayerVersionPermission"
2176 | "CreateFunctionUrlConfig"
2177 | "DeleteFunctionUrlConfig"
2178 | "UpdateFunctionUrlConfig"
2179 | "PutFunctionConcurrency"
2180 | "DeleteFunctionConcurrency"
2181 | "PutProvisionedConcurrencyConfig"
2182 | "DeleteProvisionedConcurrencyConfig"
2183 | "CreateCodeSigningConfig"
2184 | "UpdateCodeSigningConfig"
2185 | "DeleteCodeSigningConfig"
2186 | "PutFunctionCodeSigningConfig"
2187 | "DeleteFunctionCodeSigningConfig"
2188 | "PutFunctionEventInvokeConfig"
2189 | "UpdateFunctionEventInvokeConfig"
2190 | "DeleteFunctionEventInvokeConfig"
2191 | "PutRuntimeManagementConfig"
2192 | "PutFunctionScalingConfig"
2193 | "PutFunctionRecursionConfig"
2194 | "TagResource"
2195 | "UntagResource"
2196 | "InvokeAsync"
2197 | "InvokeWithResponseStream"
2198 );
2199
2200 let aid = &req.account_id;
2201 let result = match action {
2202 "CreateFunction" => self.create_function(&req),
2203 "ListFunctions" => self.list_functions(
2204 aid,
2205 req.query_params.get("FunctionVersion").map(String::as_str),
2206 ),
2207 "GetFunction" => self.get_function(
2208 &req,
2209 resource_name.as_deref().unwrap_or(""),
2210 aid,
2211 req.region.as_str(),
2212 req.query_params.get("Qualifier").map(String::as_str),
2213 ),
2214 "DeleteFunction" => self.delete_function(
2215 resource_name.as_deref().unwrap_or(""),
2216 aid,
2217 req.query_params.get("Qualifier").map(String::as_str),
2218 ),
2219 "Invoke" => {
2220 let invocation_type = InvocationType::from_header(
2221 req.headers
2222 .get("x-amz-invocation-type")
2223 .and_then(|v| v.to_str().ok()),
2224 );
2225 let qualifier = req.query_params.get("Qualifier").map(String::as_str);
2226 self.invoke(
2227 resource_name.as_deref().unwrap_or(""),
2228 &req.body,
2229 aid,
2230 invocation_type,
2231 qualifier,
2232 )
2233 .await
2234 }
2235 "InvokeAsync" => {
2236 let name = resource_name.as_deref().unwrap_or("");
2242 let accounts = self.state.read();
2243 let exists = accounts
2244 .get(aid)
2245 .map(|s| s.functions.contains_key(name))
2246 .unwrap_or(false);
2247 if !exists {
2248 Err(AwsServiceError::aws_error(
2249 StatusCode::NOT_FOUND,
2250 "ResourceNotFoundException",
2251 format!("Function not found: {}", name),
2252 ))
2253 } else {
2254 Ok(AwsResponse::json(
2255 StatusCode::ACCEPTED,
2256 json!({ "Status": 202 }).to_string(),
2257 ))
2258 }
2259 }
2260 "PublishVersion" => {
2261 self.publish_version(resource_name.as_deref().unwrap_or(""), aid, &req)
2262 }
2263 "AddPermission" => self.add_permission(resource_name.as_deref().unwrap_or(""), &req),
2264 "GetPolicy" => self.get_policy(
2265 resource_name.as_deref().unwrap_or(""),
2266 aid,
2267 req.query_params.get("Qualifier").map(String::as_str),
2268 ),
2269 "RemovePermission" => {
2270 let sid = req.path_segments.get(4).cloned().unwrap_or_default();
2272 self.remove_permission(
2273 resource_name.as_deref().unwrap_or(""),
2274 &sid,
2275 aid,
2276 req.query_params.get("Qualifier").map(String::as_str),
2277 )
2278 }
2279 "CreateEventSourceMapping" => self.create_event_source_mapping(&req),
2280 "ListEventSourceMappings" => {
2281 if let Some(fn_name) = req.query_params.get("FunctionName") {
2285 let len = fn_name.chars().count();
2286 if fn_name.is_empty() || len > 140 {
2287 return Err(AwsServiceError::aws_error(
2288 StatusCode::BAD_REQUEST,
2289 "InvalidParameterValueException",
2290 "FunctionName must be 1..140 characters",
2291 ));
2292 }
2293 }
2294 self.list_event_source_mappings(aid)
2295 }
2296 "GetEventSourceMapping" => {
2297 self.get_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
2298 }
2299 "DeleteEventSourceMapping" => {
2300 self.delete_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
2301 }
2302 other => {
2303 self.handle_extra(other, resource_name.as_deref(), &req)
2304 .await
2305 }
2306 };
2307 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
2308 self.save_snapshot().await;
2309 }
2310 result
2311 }
2312
2313 fn supported_actions(&self) -> &[&str] {
2314 &[
2315 "CreateFunction",
2316 "GetFunction",
2317 "DeleteFunction",
2318 "ListFunctions",
2319 "Invoke",
2320 "InvokeAsync",
2321 "InvokeWithResponseStream",
2322 "PublishVersion",
2323 "ListVersionsByFunction",
2324 "AddPermission",
2325 "RemovePermission",
2326 "GetPolicy",
2327 "CreateEventSourceMapping",
2328 "ListEventSourceMappings",
2329 "GetEventSourceMapping",
2330 "UpdateEventSourceMapping",
2331 "DeleteEventSourceMapping",
2332 "GetFunctionConfiguration",
2333 "UpdateFunctionConfiguration",
2334 "UpdateFunctionCode",
2335 "GetAccountSettings",
2336 "CreateAlias",
2337 "GetAlias",
2338 "ListAliases",
2339 "UpdateAlias",
2340 "DeleteAlias",
2341 "PublishLayerVersion",
2342 "GetLayerVersion",
2343 "GetLayerVersionByArn",
2344 "DeleteLayerVersion",
2345 "ListLayerVersions",
2346 "ListLayers",
2347 "GetLayerVersionPolicy",
2348 "AddLayerVersionPermission",
2349 "RemoveLayerVersionPermission",
2350 "CreateFunctionUrlConfig",
2351 "GetFunctionUrlConfig",
2352 "UpdateFunctionUrlConfig",
2353 "DeleteFunctionUrlConfig",
2354 "ListFunctionUrlConfigs",
2355 "PutFunctionConcurrency",
2356 "GetFunctionConcurrency",
2357 "DeleteFunctionConcurrency",
2358 "PutProvisionedConcurrencyConfig",
2359 "GetProvisionedConcurrencyConfig",
2360 "DeleteProvisionedConcurrencyConfig",
2361 "ListProvisionedConcurrencyConfigs",
2362 "CreateCodeSigningConfig",
2363 "GetCodeSigningConfig",
2364 "UpdateCodeSigningConfig",
2365 "DeleteCodeSigningConfig",
2366 "ListCodeSigningConfigs",
2367 "PutFunctionCodeSigningConfig",
2368 "GetFunctionCodeSigningConfig",
2369 "DeleteFunctionCodeSigningConfig",
2370 "ListFunctionsByCodeSigningConfig",
2371 "PutFunctionEventInvokeConfig",
2372 "GetFunctionEventInvokeConfig",
2373 "UpdateFunctionEventInvokeConfig",
2374 "DeleteFunctionEventInvokeConfig",
2375 "ListFunctionEventInvokeConfigs",
2376 "PutRuntimeManagementConfig",
2377 "GetRuntimeManagementConfig",
2378 "PutFunctionScalingConfig",
2379 "GetFunctionScalingConfig",
2380 "PutFunctionRecursionConfig",
2381 "GetFunctionRecursionConfig",
2382 "TagResource",
2383 "UntagResource",
2384 "ListTags",
2385 ]
2386 }
2387
2388 fn iam_enforceable(&self) -> bool {
2389 true
2390 }
2391
2392 fn iam_action_for(&self, request: &AwsRequest) -> Option<fakecloud_core::auth::IamAction> {
2396 let (action_str, resource_name) = Self::resolve_action(request)?;
2401 let action: &'static str = match action_str {
2402 "CreateFunction" => "CreateFunction",
2403 "ListFunctions" => "ListFunctions",
2404 "GetFunction" => "GetFunction",
2405 "DeleteFunction" => "DeleteFunction",
2406 "Invoke" => "InvokeFunction",
2407 "InvokeWithResponseStream" => "InvokeFunctionWithResponseStream",
2408 "PublishVersion" => "PublishVersion",
2409 "AddPermission" => "AddPermission",
2410 "RemovePermission" => "RemovePermission",
2411 "GetPolicy" => "GetPolicy",
2412 "CreateEventSourceMapping" => "CreateEventSourceMapping",
2413 "ListEventSourceMappings" => "ListEventSourceMappings",
2414 "GetEventSourceMapping" => "GetEventSourceMapping",
2415 "DeleteEventSourceMapping" => "DeleteEventSourceMapping",
2416 _ => return None,
2417 };
2418 let accounts = self.state.read();
2419 let empty = LambdaState::new(&request.account_id, &request.region);
2420 let state = accounts.get(&request.account_id).unwrap_or(&empty);
2421 let resource = match action {
2422 "GetFunction"
2423 | "DeleteFunction"
2424 | "InvokeFunction"
2425 | "InvokeFunctionWithResponseStream"
2426 | "PublishVersion"
2427 | "AddPermission"
2428 | "RemovePermission"
2429 | "GetPolicy" => {
2430 let name = resource_name.unwrap_or_default();
2431 if name.is_empty() {
2432 "*".to_string()
2433 } else {
2434 format!(
2435 "arn:aws:lambda:{}:{}:function:{}",
2436 state.region, state.account_id, name
2437 )
2438 }
2439 }
2440 "CreateFunction" => {
2441 serde_json::from_slice::<Value>(&request.body)
2446 .ok()
2447 .and_then(|v| {
2448 v.get("FunctionName").and_then(|f| f.as_str()).map(|n| {
2449 format!(
2450 "arn:aws:lambda:{}:{}:function:{}",
2451 state.region, state.account_id, n
2452 )
2453 })
2454 })
2455 .unwrap_or_else(|| "*".to_string())
2456 }
2457 _ => "*".to_string(),
2458 };
2459 Some(fakecloud_core::auth::IamAction {
2460 service: "lambda",
2461 action,
2462 resource,
2463 })
2464 }
2465
2466 fn iam_condition_keys_for(
2467 &self,
2468 request: &AwsRequest,
2469 action: &fakecloud_core::auth::IamAction,
2470 ) -> std::collections::BTreeMap<String, Vec<String>> {
2471 let mut out = std::collections::BTreeMap::new();
2472 if action.action == "AddPermission" {
2473 if action.resource != "*" {
2474 out.insert(
2475 "lambda:functionarn".to_string(),
2476 vec![action.resource.clone()],
2477 );
2478 }
2479 if let Ok(body) = serde_json::from_slice::<Value>(&request.body) {
2480 if let Some(principal) = body.get("Principal").and_then(|p| p.as_str()) {
2481 out.insert("lambda:principal".to_string(), vec![principal.to_string()]);
2482 }
2483 }
2484 }
2485 out
2486 }
2487}
2488
2489#[path = "service_event_sources.rs"]
2490mod service_event_sources;
2491#[path = "service_permissions.rs"]
2492mod service_permissions;
2493
2494#[cfg(test)]
2495#[path = "service_tests.rs"]
2496mod tests;