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 | "PutFunctionRecursionConfig"
62 | "GetFunctionRecursionConfig"
63 | "CreateAlias"
64 | "GetAlias"
65 | "ListAliases"
66 | "UpdateAlias"
67 | "DeleteAlias"
68 | "PutRuntimeManagementConfig"
69 | "GetRuntimeManagementConfig"
70 | "ListDurableExecutionsByFunction"
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
141struct CreateFunctionInput {
145 function_name: String,
146 runtime: String,
147 role: String,
148 handler: String,
149 description: String,
150 timeout: i64,
151 memory_size: i64,
152 package_type: String,
153 tags: BTreeMap<String, String>,
154 environment: BTreeMap<String, String>,
155 architectures: Vec<String>,
156 code_zip: Option<Vec<u8>>,
157 code_fallback: Vec<u8>,
158 image_uri: Option<String>,
159 layer_arns: Vec<String>,
160}
161
162impl CreateFunctionInput {
163 fn from_body(body: &Value) -> Result<Self, AwsServiceError> {
164 let function_name = body["FunctionName"]
165 .as_str()
166 .ok_or_else(|| {
167 AwsServiceError::aws_error(
168 StatusCode::BAD_REQUEST,
169 "InvalidParameterValueException",
170 "FunctionName is required",
171 )
172 })?
173 .to_string();
174
175 let tags: BTreeMap<String, String> = body["Tags"]
176 .as_object()
177 .map(|m| {
178 m.iter()
179 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
180 .collect()
181 })
182 .unwrap_or_default();
183
184 let environment: BTreeMap<String, String> = body["Environment"]["Variables"]
185 .as_object()
186 .map(|m| {
187 m.iter()
188 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
189 .collect()
190 })
191 .unwrap_or_default();
192
193 let architectures = body["Architectures"]
194 .as_array()
195 .map(|a| {
196 a.iter()
197 .filter_map(|v| v.as_str().map(|s| s.to_string()))
198 .collect()
199 })
200 .unwrap_or_else(|| vec!["x86_64".to_string()]);
201
202 let code_zip: Option<Vec<u8>> = match body["Code"]["ZipFile"].as_str() {
203 Some(b64) => Some(
204 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64).map_err(
205 |_| {
206 AwsServiceError::aws_error(
207 StatusCode::BAD_REQUEST,
208 "InvalidParameterValueException",
209 "Could not decode Code.ZipFile: invalid base64",
210 )
211 },
212 )?,
213 ),
214 None => None,
215 };
216
217 let code_fallback = serde_json::to_vec(&body["Code"]).unwrap_or_default();
218
219 let package_type = body["PackageType"].as_str().unwrap_or("Zip").to_string();
220 let image_uri = if package_type == "Image" {
225 body["Code"]["ImageUri"].as_str().map(String::from)
226 } else {
227 None
228 };
229
230 if package_type == "Image" && image_uri.is_none() {
234 return Err(AwsServiceError::aws_error(
235 StatusCode::BAD_REQUEST,
236 "InvalidParameterValueException",
237 "Code.ImageUri is required when PackageType is Image",
238 ));
239 }
240
241 let layer_arns: Vec<String> = body["Layers"]
242 .as_array()
243 .map(|arr| {
244 arr.iter()
245 .filter_map(|v| v.as_str().map(String::from))
246 .collect()
247 })
248 .unwrap_or_default();
249
250 Ok(Self {
251 function_name,
252 runtime: body["Runtime"].as_str().unwrap_or("python3.12").to_string(),
253 role: body["Role"].as_str().unwrap_or("").to_string(),
254 handler: body["Handler"]
255 .as_str()
256 .unwrap_or("index.handler")
257 .to_string(),
258 description: body["Description"].as_str().unwrap_or("").to_string(),
259 timeout: body["Timeout"].as_i64().unwrap_or(3),
260 memory_size: body["MemorySize"].as_i64().unwrap_or(128),
261 package_type,
262 tags,
263 environment,
264 architectures,
265 code_zip,
266 code_fallback,
267 image_uri,
268 layer_arns,
269 })
270 }
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum InvocationType {
276 RequestResponse,
277 Event,
278 DryRun,
279}
280
281impl InvocationType {
282 pub fn from_header(value: Option<&str>) -> Self {
283 match value {
284 Some("Event") => Self::Event,
285 Some("DryRun") => Self::DryRun,
286 _ => Self::RequestResponse,
287 }
288 }
289}
290
291fn route_to_destination(
295 bus: Arc<fakecloud_core::delivery::DeliveryBus>,
296 function_arn: &str,
297 request_payload: &[u8],
298 result: &Result<Vec<u8>, String>,
299 destination_config: Option<&serde_json::Value>,
300) {
301 let Some(cfg) = destination_config else {
302 return;
303 };
304 let (key, condition, response_value): (&str, &str, serde_json::Value) = match result {
305 Ok(bytes) => (
306 "OnSuccess",
307 "Success",
308 serde_json::from_slice(bytes).unwrap_or(serde_json::Value::Null),
309 ),
310 Err(err) => (
311 "OnFailure",
312 "RetriesExhausted",
313 serde_json::json!({ "errorMessage": err }),
314 ),
315 };
316 let Some(dest) = cfg
317 .get(key)
318 .and_then(|v| v.get("Destination"))
319 .and_then(|v| v.as_str())
320 else {
321 return;
322 };
323 let request_payload_v: serde_json::Value =
324 serde_json::from_slice(request_payload).unwrap_or(serde_json::Value::Null);
325 let record = serde_json::json!({
326 "version": "1.0",
327 "timestamp": chrono::Utc::now().to_rfc3339(),
328 "requestContext": {
329 "requestId": uuid::Uuid::new_v4().to_string(),
330 "functionArn": format!("{function_arn}:$LATEST"),
331 "condition": condition,
332 "approximateInvokeCount": 1,
333 },
334 "requestPayload": request_payload_v,
335 "responseContext": {
336 "statusCode": 200,
337 "executedVersion": "$LATEST",
338 },
339 "responsePayload": response_value,
340 });
341 let body = record.to_string();
342 if dest.contains(":sqs:") {
343 bus.send_to_sqs(dest, &body, &std::collections::HashMap::new());
344 } else if dest.contains(":sns:") {
345 bus.publish_to_sns(dest, &body, None);
346 } else if dest.contains(":lambda:") {
347 let dest = dest.to_string();
348 let payload = body.clone();
349 tokio::spawn(async move {
350 let _ = bus.invoke_lambda(&dest, &payload).await;
351 });
352 } else if dest.contains(":events:") || dest.contains(":eventbridge:") {
353 let detail_type = if result.is_ok() {
354 "Lambda Function Invocation Result - Success"
355 } else {
356 "Lambda Function Invocation Result - Failure"
357 };
358 bus.put_event_to_eventbridge("lambda", detail_type, &body, "default");
359 }
360}
361
362pub struct LambdaService {
363 pub(crate) state: SharedLambdaState,
364 runtime: Option<Arc<ContainerRuntime>>,
365 snapshot_store: Option<Arc<dyn SnapshotStore>>,
366 snapshot_lock: Arc<AsyncMutex<()>>,
367 pub(crate) delivery_bus: Option<Arc<fakecloud_core::delivery::DeliveryBus>>,
368 pub(crate) role_trust_validator: Option<Arc<dyn fakecloud_core::auth::RoleTrustValidator>>,
369}
370
371impl LambdaService {
372 pub fn new(state: SharedLambdaState) -> Self {
373 Self {
374 state,
375 runtime: None,
376 snapshot_store: None,
377 snapshot_lock: Arc::new(AsyncMutex::new(())),
378 delivery_bus: None,
379 role_trust_validator: None,
380 }
381 }
382
383 pub fn with_runtime(mut self, runtime: Arc<ContainerRuntime>) -> Self {
384 self.runtime = Some(runtime);
385 self
386 }
387
388 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
389 self.snapshot_store = Some(store);
390 self
391 }
392
393 pub fn with_delivery_bus(mut self, bus: Arc<fakecloud_core::delivery::DeliveryBus>) -> Self {
394 self.delivery_bus = Some(bus);
395 self
396 }
397
398 pub fn with_role_trust_validator(
399 mut self,
400 validator: Arc<dyn fakecloud_core::auth::RoleTrustValidator>,
401 ) -> Self {
402 self.role_trust_validator = Some(validator);
403 self
404 }
405
406 async fn save_snapshot(&self) {
407 let Some(store) = self.snapshot_store.clone() else {
408 return;
409 };
410 let _guard = self.snapshot_lock.lock().await;
411 let snapshot = LambdaSnapshot {
412 schema_version: LAMBDA_SNAPSHOT_SCHEMA_VERSION,
413 accounts: Some(self.state.read().clone()),
414 state: None,
415 };
416 let join = tokio::task::spawn_blocking(move || -> std::io::Result<()> {
417 let bytes = serde_json::to_vec(&snapshot)
418 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
419 store.save(&bytes)
420 })
421 .await;
422 match join {
423 Ok(Ok(())) => {}
424 Ok(Err(err)) => tracing::error!(%err, "failed to write lambda snapshot"),
425 Err(err) => tracing::error!(%err, "lambda snapshot task panicked"),
426 }
427 }
428
429 fn resolve_action(req: &AwsRequest) -> Option<(&'static str, Option<String>)> {
442 let segs = &req.path_segments;
443 if segs.is_empty() {
444 return None;
445 }
446 let prefix = segs[0].as_str();
450
451 if segs.get(1).map(|s| s.as_str()) == Some("account-settings") && req.method == Method::GET
453 {
454 return Some(("GetAccountSettings", None));
455 }
456 if segs.get(1).map(|s| s.as_str()) == Some("functions")
457 && segs.get(3).map(|s| s.as_str()) == Some("invoke-async")
458 && req.method == Method::POST
459 {
460 return Some(("InvokeAsync", segs.get(2).map(|s| s.to_string())));
461 }
462 if segs.get(1).map(|s| s.as_str()) == Some("functions")
463 && segs.get(3).map(|s| s.as_str()) == Some("response-streaming-invocations")
464 && req.method == Method::POST
465 {
466 return Some((
467 "InvokeWithResponseStream",
468 segs.get(2).map(|s| s.to_string()),
469 ));
470 }
471
472 if segs.get(1).map(|s| s.as_str()) == Some("functions")
474 && segs.get(3).map(|s| s.as_str()) == Some("concurrency")
475 {
476 let res = segs.get(2).map(|s| s.to_string());
477 return match req.method {
478 Method::PUT => Some(("PutFunctionConcurrency", res)),
479 Method::GET => Some(("GetFunctionConcurrency", res)),
480 Method::DELETE => Some(("DeleteFunctionConcurrency", res)),
481 _ => None,
482 };
483 }
484
485 if segs.get(1).map(|s| s.as_str()) == Some("functions")
487 && segs.get(3).map(|s| s.as_str()) == Some("provisioned-concurrency")
488 {
489 let res = segs.get(2).map(|s| s.to_string());
490 return match req.method {
491 Method::PUT => Some(("PutProvisionedConcurrencyConfig", res)),
492 Method::GET => Some(("GetProvisionedConcurrencyConfig", res)),
493 Method::DELETE => Some(("DeleteProvisionedConcurrencyConfig", res)),
494 _ => None,
495 };
496 }
497 if segs.get(1).map(|s| s.as_str()) == Some("functions")
498 && segs.get(3).map(|s| s.as_str()) == Some("provisioned-concurrency-configs")
499 && req.method == Method::GET
500 {
501 return Some((
502 "ListProvisionedConcurrencyConfigs",
503 segs.get(2).map(|s| s.to_string()),
504 ));
505 }
506
507 if segs.get(1).map(|s| s.as_str()) == Some("functions")
509 && segs.get(3).map(|s| s.as_str()) == Some("event-invoke-config")
510 {
511 let res = segs.get(2).map(|s| s.to_string());
512 return match req.method {
513 Method::POST => Some(("PutFunctionEventInvokeConfig", res)),
514 Method::PUT => Some(("UpdateFunctionEventInvokeConfig", res)),
515 Method::GET => Some(("GetFunctionEventInvokeConfig", res)),
516 Method::DELETE => Some(("DeleteFunctionEventInvokeConfig", res)),
517 _ => None,
518 };
519 }
520 if segs.get(1).map(|s| s.as_str()) == Some("functions")
521 && (segs.get(3).map(|s| s.as_str()) == Some("event-invoke-config-list")
522 || (segs.get(3).map(|s| s.as_str()) == Some("event-invoke-config")
523 && segs.get(4).map(|s| s.as_str()) == Some("list")))
524 && req.method == Method::GET
525 {
526 return Some((
527 "ListFunctionEventInvokeConfigs",
528 segs.get(2).map(|s| s.to_string()),
529 ));
530 }
531
532 if segs.get(1).map(|s| s.as_str()) == Some("functions")
534 && segs.get(3).map(|s| s.as_str()) == Some("recursion-config")
535 {
536 let res = segs.get(2).map(|s| s.to_string());
537 return match req.method {
538 Method::PUT => Some(("PutFunctionRecursionConfig", res)),
539 Method::GET => Some(("GetFunctionRecursionConfig", res)),
540 _ => None,
541 };
542 }
543
544 if segs.get(1).map(|s| s.as_str()) == Some("functions")
546 && segs.get(3).map(|s| s.as_str()) == Some("runtime-management-config")
547 {
548 let res = segs.get(2).map(|s| s.to_string());
549 return match req.method {
550 Method::PUT => Some(("PutRuntimeManagementConfig", res)),
551 Method::GET => Some(("GetRuntimeManagementConfig", res)),
552 _ => None,
553 };
554 }
555
556 if segs.get(1).map(|s| s.as_str()) == Some("functions")
558 && segs.get(3).map(|s| s.as_str()) == Some("code-signing-config")
559 {
560 let res = segs.get(2).map(|s| s.to_string());
561 return match req.method {
562 Method::PUT => Some(("PutFunctionCodeSigningConfig", res)),
563 Method::GET => Some(("GetFunctionCodeSigningConfig", res)),
564 Method::DELETE => Some(("DeleteFunctionCodeSigningConfig", res)),
565 _ => None,
566 };
567 }
568 if segs.get(1).map(|s| s.as_str()) == Some("code-signing-configs") {
569 let res = segs.get(2).map(|s| s.to_string());
570 return match (
571 req.method.clone(),
572 segs.len(),
573 segs.get(3).map(|s| s.as_str()),
574 ) {
575 (Method::POST, 2, _) => Some(("CreateCodeSigningConfig", None)),
576 (Method::GET, 2, _) => Some(("ListCodeSigningConfigs", None)),
577 (Method::GET, 3, _) => Some(("GetCodeSigningConfig", res)),
578 (Method::PUT, 3, _) => Some(("UpdateCodeSigningConfig", res)),
579 (Method::DELETE, 3, _) => Some(("DeleteCodeSigningConfig", res)),
580 (Method::GET, 4, Some("functions")) => {
581 Some(("ListFunctionsByCodeSigningConfig", res))
582 }
583 _ => None,
584 };
585 }
586
587 if segs.get(1).map(|s| s.as_str()) == Some("tags") && segs.len() >= 3 {
589 let res = segs[2..].join("/");
590 return match req.method {
591 Method::POST => Some(("TagResource", Some(res))),
592 Method::DELETE => Some(("UntagResource", Some(res))),
593 Method::GET => Some(("ListTags", Some(res))),
594 _ => None,
595 };
596 }
597
598 if segs.get(1).map(|s| s.as_str()) == Some("functions")
600 && segs.get(3).map(|s| s.as_str()) == Some("url")
601 {
602 let res = segs.get(2).map(|s| s.to_string());
603 return match req.method {
604 Method::POST => Some(("CreateFunctionUrlConfig", res)),
605 Method::GET => Some(("GetFunctionUrlConfig", res)),
606 Method::PUT => Some(("UpdateFunctionUrlConfig", res)),
607 Method::DELETE => Some(("DeleteFunctionUrlConfig", res)),
608 _ => None,
609 };
610 }
611 if segs.get(1).map(|s| s.as_str()) == Some("function-urls") && req.method == Method::GET {
612 return Some(("ListFunctionUrlConfigs", None));
613 }
614 if segs.get(1).map(|s| s.as_str()) == Some("functions")
615 && segs.get(3).map(|s| s.as_str()) == Some("urls")
616 && req.method == Method::GET
617 {
618 return Some(("ListFunctionUrlConfigs", segs.get(2).map(|s| s.to_string())));
619 }
620 if segs.get(1).map(|s| s.as_str()) == Some("event-source-mappings")
621 && segs.get(3).map(|s| s.as_str()) == Some("scaling-config")
622 {
623 let res = segs.get(2).map(|s| s.to_string());
624 return match req.method {
625 Method::PUT => Some(("PutFunctionScalingConfig", res)),
626 Method::GET => Some(("GetFunctionScalingConfig", res)),
627 _ => None,
628 };
629 }
630
631 if segs.get(1).map(|s| s.as_str()) == Some("capacity-providers") {
633 let res = segs.get(2).map(|s| s.to_string());
634 return match (
635 req.method.clone(),
636 segs.len(),
637 segs.get(3).map(|s| s.as_str()),
638 ) {
639 (Method::POST, 2, _) => Some(("CreateCapacityProvider", None)),
640 (Method::GET, 2, _) => Some(("ListCapacityProviders", None)),
641 (Method::GET, 3, _) => Some(("GetCapacityProvider", res)),
642 (Method::PUT, 3, _) => Some(("UpdateCapacityProvider", res)),
643 (Method::DELETE, 3, _) => Some(("DeleteCapacityProvider", res)),
644 (Method::GET, 4, Some("function-versions")) => {
645 Some(("ListFunctionVersionsByCapacityProvider", res))
646 }
647 _ => None,
648 };
649 }
650
651 if segs.get(1).map(|s| s.as_str()) == Some("functions")
653 && segs.get(3).map(|s| s.as_str()) == Some("durable-executions")
654 && req.method == Method::GET
655 {
656 return Some((
657 "ListDurableExecutionsByFunction",
658 segs.get(2).map(|s| s.to_string()),
659 ));
660 }
661
662 if segs.get(1).map(|s| s.as_str()) == Some("durable-execution-callbacks")
664 && req.method == Method::POST
665 {
666 let res = segs.get(2).map(|s| s.to_string());
667 return match segs.get(3).map(|s| s.as_str()) {
668 Some("success") | Some("succeed") => {
669 Some(("SendDurableExecutionCallbackSuccess", res))
670 }
671 Some("failure") | Some("fail") => {
672 Some(("SendDurableExecutionCallbackFailure", res))
673 }
674 Some("heartbeat") => Some(("SendDurableExecutionCallbackHeartbeat", res)),
675 _ => None,
676 };
677 }
678
679 if segs.get(1).map(|s| s.as_str()) == Some("durable-executions") {
681 let res = segs.get(2).map(|s| s.to_string());
682 return match (
683 req.method.clone(),
684 segs.len(),
685 segs.get(3).map(|s| s.as_str()),
686 segs.get(4).map(|s| s.as_str()),
687 ) {
688 (Method::GET, 3, _, _) => Some(("GetDurableExecution", res)),
689 (Method::GET, 4, Some("history"), _) => Some(("GetDurableExecutionHistory", res)),
690 (Method::GET, 4, Some("state"), _) => Some(("GetDurableExecutionState", res)),
691 (Method::POST, 4, Some("checkpoint"), _) => {
692 Some(("CheckpointDurableExecution", res))
693 }
694 (Method::POST, 4, Some("stop"), _) => Some(("StopDurableExecution", res)),
695 (Method::POST, 5, Some("callback"), Some("success")) => {
696 Some(("SendDurableExecutionCallbackSuccess", res))
697 }
698 (Method::POST, 5, Some("callback"), Some("failure")) => {
699 Some(("SendDurableExecutionCallbackFailure", res))
700 }
701 (Method::POST, 5, Some("callback"), Some("heartbeat")) => {
702 Some(("SendDurableExecutionCallbackHeartbeat", res))
703 }
704 _ => None,
705 };
706 }
707
708 if prefix == "2018-10-31" && segs.get(1).map(|s| s.as_str()) == Some("layers") {
715 let layer = segs.get(2).map(|s| s.to_string());
716 let third = segs.get(3).map(|s| s.as_str());
717 let version = segs.get(4).map(|s| s.to_string());
718 return match (&req.method, segs.len(), third, version.is_some()) {
719 (&Method::GET, 2, _, _) => Some(("ListLayers", None)),
720 (&Method::POST, 4, Some("versions"), false) => Some(("PublishLayerVersion", layer)),
721 (&Method::GET, 4, Some("versions"), false) => Some(("ListLayerVersions", layer)),
722 (&Method::GET, 5, Some("versions"), true) => Some(("GetLayerVersion", version)),
723 (&Method::DELETE, 5, Some("versions"), true) => {
724 Some(("DeleteLayerVersion", version))
725 }
726 (&Method::GET, 6, Some("versions"), true)
727 if segs.get(5).map(|s| s.as_str()) == Some("policy") =>
728 {
729 Some(("GetLayerVersionPolicy", version))
730 }
731 (&Method::POST, 6, Some("versions"), true)
732 if segs.get(5).map(|s| s.as_str()) == Some("policy") =>
733 {
734 Some(("AddLayerVersionPermission", version))
735 }
736 (&Method::DELETE, 7, Some("versions"), true)
737 if segs.get(5).map(|s| s.as_str()) == Some("policy") =>
738 {
739 Some(("RemoveLayerVersionPermission", version))
740 }
741 _ => None,
742 };
743 }
744
745 if prefix == "2018-10-31"
747 && segs.get(1).map(|s| s.as_str()) == Some("layers-by-arn")
748 && req.method == Method::GET
749 {
750 return Some(("GetLayerVersionByArn", None));
751 }
752
753 if prefix != "2015-03-31" {
757 return None;
758 }
759
760 let collection = segs.get(1).map(|s| s.as_str());
761 let resource = segs.get(2).map(|s| s.to_string());
762 let third = segs.get(3).map(|s| s.as_str());
763 let fourth = segs.get(4).map(|s| s.as_str());
764
765 let action = match (&req.method, segs.len(), collection, third) {
766 (&Method::POST, 2, Some("functions"), _) => "CreateFunction",
767 (&Method::GET, 2, Some("functions"), _) => "ListFunctions",
768 (&Method::GET, 3, Some("functions"), _) => "GetFunction",
769 (&Method::DELETE, 3, Some("functions"), _) => "DeleteFunction",
770 (&Method::POST, 4, Some("functions"), Some("invocations")) => "Invoke",
771 (&Method::POST, 4, Some("functions"), Some("invoke-async")) => "InvokeAsync",
772 (&Method::POST, 4, Some("functions"), Some("response-streaming-invocations")) => {
773 "InvokeWithResponseStream"
774 }
775 (&Method::POST, 4, Some("functions"), Some("versions")) => "PublishVersion",
776 (&Method::GET, 4, Some("functions"), Some("versions")) => "ListVersionsByFunction",
777 (&Method::POST, 4, Some("functions"), Some("policy")) => "AddPermission",
778 (&Method::GET, 4, Some("functions"), Some("policy")) => "GetPolicy",
779 (&Method::DELETE, 5, Some("functions"), Some("policy")) => "RemovePermission",
780 (&Method::POST, 4, Some("functions"), Some("aliases")) => "CreateAlias",
781 (&Method::GET, 4, Some("functions"), Some("aliases")) => "ListAliases",
782 (&Method::GET, 5, Some("functions"), Some("aliases")) => "GetAlias",
783 (&Method::PUT, 5, Some("functions"), Some("aliases")) => "UpdateAlias",
784 (&Method::DELETE, 5, Some("functions"), Some("aliases")) => "DeleteAlias",
785 (&Method::GET, 4, Some("functions"), Some("configuration")) => {
786 "GetFunctionConfiguration"
787 }
788 (&Method::PUT, 4, Some("functions"), Some("configuration")) => {
789 "UpdateFunctionConfiguration"
790 }
791 (&Method::PUT, 4, Some("functions"), Some("code")) => "UpdateFunctionCode",
792 (&Method::PUT, 4, Some("functions"), Some("concurrency")) => "PutFunctionConcurrency",
793 (&Method::GET, 4, Some("functions"), Some("concurrency")) => "GetFunctionConcurrency",
794 (&Method::DELETE, 4, Some("functions"), Some("concurrency")) => {
795 "DeleteFunctionConcurrency"
796 }
797 (&Method::PUT, 4, Some("functions"), Some("provisioned-concurrency")) => {
798 "PutProvisionedConcurrencyConfig"
799 }
800 (&Method::GET, 4, Some("functions"), Some("provisioned-concurrency")) => {
801 "GetProvisionedConcurrencyConfig"
802 }
803 (&Method::DELETE, 4, Some("functions"), Some("provisioned-concurrency")) => {
804 "DeleteProvisionedConcurrencyConfig"
805 }
806 (&Method::GET, 4, Some("functions"), Some("provisioned-concurrency-configs")) => {
807 "ListProvisionedConcurrencyConfigs"
808 }
809 (&Method::PUT, 4, Some("functions"), Some("event-invoke-config")) => {
810 "UpdateFunctionEventInvokeConfig"
811 }
812 (&Method::POST, 4, Some("functions"), Some("event-invoke-config")) => {
813 "PutFunctionEventInvokeConfig"
814 }
815 (&Method::GET, 4, Some("functions"), Some("event-invoke-config")) => {
816 "GetFunctionEventInvokeConfig"
817 }
818 (&Method::DELETE, 4, Some("functions"), Some("event-invoke-config")) => {
819 "DeleteFunctionEventInvokeConfig"
820 }
821 (&Method::GET, 4, Some("functions"), Some("event-invoke-config-list")) => {
822 "ListFunctionEventInvokeConfigs"
823 }
824 (&Method::PUT, 4, Some("functions"), Some("code-signing-config")) => {
825 "PutFunctionCodeSigningConfig"
826 }
827 (&Method::GET, 4, Some("functions"), Some("code-signing-config")) => {
828 "GetFunctionCodeSigningConfig"
829 }
830 (&Method::DELETE, 4, Some("functions"), Some("code-signing-config")) => {
831 "DeleteFunctionCodeSigningConfig"
832 }
833 (&Method::PUT, 4, Some("functions"), Some("runtime-management-config")) => {
834 "PutRuntimeManagementConfig"
835 }
836 (&Method::GET, 4, Some("functions"), Some("runtime-management-config")) => {
837 "GetRuntimeManagementConfig"
838 }
839 (&Method::PUT, 4, Some("functions"), Some("scaling-config")) => {
840 "PutFunctionScalingConfig"
841 }
842 (&Method::GET, 4, Some("functions"), Some("scaling-config")) => {
843 "GetFunctionScalingConfig"
844 }
845 (&Method::PUT, 4, Some("functions"), Some("recursion-config")) => {
846 "PutFunctionRecursionConfig"
847 }
848 (&Method::GET, 4, Some("functions"), Some("recursion-config")) => {
849 "GetFunctionRecursionConfig"
850 }
851 (&Method::GET, 4, Some("functions"), Some("durable-executions")) => {
852 "ListDurableExecutionsByFunction"
853 }
854 (&Method::POST, 2, Some("event-source-mappings"), _) => "CreateEventSourceMapping",
855 (&Method::GET, 2, Some("event-source-mappings"), _) => "ListEventSourceMappings",
856 (&Method::GET, 3, Some("event-source-mappings"), _) => "GetEventSourceMapping",
857 (&Method::PUT, 3, Some("event-source-mappings"), _) => "UpdateEventSourceMapping",
858 (&Method::DELETE, 3, Some("event-source-mappings"), _) => "DeleteEventSourceMapping",
859 (&Method::POST, 3, Some("tags"), _) => "TagResource",
860 (&Method::DELETE, 3, Some("tags"), _) => "UntagResource",
861 (&Method::GET, 3, Some("tags"), _) => "ListTags",
862 _ => return None,
863 };
864 let _ = fourth;
865
866 Some((action, resource))
867 }
868
869 fn create_function(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
870 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
871 let input = CreateFunctionInput::from_body(&body)?;
872
873 if let Some(ref validator) = self.role_trust_validator {
878 if let Err(err) =
879 validator.validate(&req.account_id, &input.role, "lambda.amazonaws.com")
880 {
881 return Err(AwsServiceError::aws_error(
882 StatusCode::BAD_REQUEST,
883 "InvalidParameterValueException",
884 err.to_string(),
885 ));
886 }
887 }
888
889 let mut accounts = self.state.write();
890 let layer_attachments =
893 crate::extras::resolve_layer_attachments(&accounts, input.layer_arns.clone());
894 let state = accounts.get_or_create(&req.account_id);
895
896 if state.functions.contains_key(&input.function_name) {
897 return Err(AwsServiceError::aws_error(
898 StatusCode::CONFLICT,
899 "ResourceConflictException",
900 format!("Function already exist: {}", input.function_name),
901 ));
902 }
903
904 let code_bytes = input.code_zip.as_deref().unwrap_or(&input.code_fallback);
907 let mut hasher = Sha256::new();
908 hasher.update(code_bytes);
909 let hash = hasher.finalize();
910 let code_sha256 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, hash);
911 let code_size = code_bytes.len() as i64;
912
913 let function_arn = format!(
914 "arn:aws:lambda:{}:{}:function:{}",
915 state.region, state.account_id, input.function_name
916 );
917 let now = Utc::now();
918
919 let func = LambdaFunction {
920 function_name: input.function_name.clone(),
921 function_arn,
922 runtime: input.runtime,
923 role: input.role,
924 handler: input.handler,
925 description: input.description,
926 timeout: input.timeout,
927 memory_size: input.memory_size,
928 code_sha256,
929 code_size,
930 version: "$LATEST".to_string(),
931 last_modified: now,
932 tags: input.tags,
933 environment: input.environment,
934 architectures: input.architectures,
935 package_type: input.package_type,
936 code_zip: input.code_zip,
937 image_uri: input.image_uri,
938 policy: None,
939 layers: layer_attachments,
940 };
941
942 let response = self.function_config_json(&func);
943
944 state.functions.insert(input.function_name, func);
945
946 Ok(AwsResponse::json(StatusCode::CREATED, response.to_string()))
947 }
948
949 fn get_function(
950 &self,
951 function_name: &str,
952 account_id: &str,
953 region: &str,
954 ) -> Result<AwsResponse, AwsServiceError> {
955 let accounts = self.state.read();
956 let empty = LambdaState::new(account_id, region);
957 let state = accounts.get(account_id).unwrap_or(&empty);
958 let func = state.functions.get(function_name).ok_or_else(|| {
959 AwsServiceError::aws_error(
960 StatusCode::NOT_FOUND,
961 "ResourceNotFoundException",
962 format!(
963 "Function not found: arn:aws:lambda:{}:{}:function:{}",
964 state.region, state.account_id, function_name
965 ),
966 )
967 })?;
968
969 let config = self.function_config_json(func);
970 let code = if let Some(ref uri) = func.image_uri {
971 json!({
972 "ImageUri": uri,
973 "ResolvedImageUri": uri,
974 "RepositoryType": "ECR",
975 })
976 } else {
977 json!({
978 "Location": format!(
979 "https://awslambda-{}-tasks.s3.{}.amazonaws.com/stub",
980 func.function_arn.split(':').nth(3).unwrap_or("us-east-1"),
981 func.function_arn.split(':').nth(3).unwrap_or("us-east-1")
982 ),
983 "RepositoryType": "S3",
984 })
985 };
986 let response = json!({
987 "Code": code,
988 "Configuration": config,
989 "Tags": func.tags,
990 });
991
992 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
993 }
994
995 fn delete_function(
996 &self,
997 function_name: &str,
998 account_id: &str,
999 ) -> Result<AwsResponse, AwsServiceError> {
1000 let mut accounts = self.state.write();
1001 let state = accounts.get_or_create(account_id);
1002 let region = state.region.clone();
1003 let account_id = state.account_id.clone();
1004 if state.functions.remove(function_name).is_none() {
1005 return Err(AwsServiceError::aws_error(
1006 StatusCode::NOT_FOUND,
1007 "ResourceNotFoundException",
1008 format!(
1009 "Function not found: arn:aws:lambda:{}:{}:function:{}",
1010 region, account_id, function_name
1011 ),
1012 ));
1013 }
1014
1015 if let Some(ref runtime) = self.runtime {
1017 let rt = runtime.clone();
1018 let name = function_name.to_string();
1019 tokio::spawn(async move { rt.stop_container(&name).await });
1020 }
1021
1022 Ok(AwsResponse::json(StatusCode::NO_CONTENT, ""))
1023 }
1024
1025 fn list_functions(&self, account_id: &str) -> Result<AwsResponse, AwsServiceError> {
1026 let accounts = self.state.read();
1027 let empty = LambdaState::new(account_id, "");
1028 let state = accounts.get(account_id).unwrap_or(&empty);
1029 let functions: Vec<Value> = state
1030 .functions
1031 .values()
1032 .map(|f| self.function_config_json(f))
1033 .collect();
1034
1035 let response = json!({
1036 "Functions": functions,
1037 });
1038
1039 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1040 }
1041
1042 async fn invoke(
1043 &self,
1044 function_name: &str,
1045 payload: &[u8],
1046 account_id: &str,
1047 invocation_type: InvocationType,
1048 ) -> Result<AwsResponse, AwsServiceError> {
1049 let (func, layer_zips) = {
1050 let accounts = self.state.read();
1051 let empty = LambdaState::new(account_id, "");
1052 let state = accounts.get(account_id).unwrap_or(&empty);
1053 let func = state.functions.get(function_name).cloned().ok_or_else(|| {
1054 AwsServiceError::aws_error(
1055 StatusCode::NOT_FOUND,
1056 "ResourceNotFoundException",
1057 format!(
1058 "Function not found: arn:aws:lambda:{}:{}:function:{}",
1059 state.region, state.account_id, function_name
1060 ),
1061 )
1062 })?;
1063 let mut layer_zips: Vec<Vec<u8>> = Vec::with_capacity(func.layers.len());
1068 for attached in &func.layers {
1069 let bytes = crate::extras::parse_layer_version_arn(&attached.arn).and_then(
1070 |(acct, name, ver)| {
1071 accounts
1072 .get(&acct)
1073 .and_then(|s| s.layers.get(&name))
1074 .and_then(|l| l.versions.iter().find(|v| v.version == ver))
1075 .and_then(|v| v.code_zip.clone())
1076 },
1077 );
1078 match bytes {
1079 Some(b) => layer_zips.push(b),
1080 None => tracing::warn!(
1081 function = %function_name,
1082 layer_arn = %attached.arn,
1083 "attached layer not resolvable; skipping /opt mount for this layer"
1084 ),
1085 }
1086 }
1087 (func, layer_zips)
1088 };
1089
1090 if func.code_zip.is_none() {
1091 return Err(AwsServiceError::aws_error(
1092 StatusCode::BAD_REQUEST,
1093 "InvalidParameterValueException",
1094 "Function has no deployment package",
1095 ));
1096 }
1097
1098 if matches!(invocation_type, InvocationType::DryRun) {
1099 let mut resp = AwsResponse::json(StatusCode::NO_CONTENT, "");
1100 resp.headers.insert(
1101 http::header::HeaderName::from_static("x-amz-executed-version"),
1102 http::header::HeaderValue::from_static("$LATEST"),
1103 );
1104 return Ok(resp);
1105 }
1106
1107 let runtime = self.runtime.as_ref().ok_or_else(|| {
1108 AwsServiceError::aws_error(
1109 StatusCode::INTERNAL_SERVER_ERROR,
1110 "ServiceException",
1111 "Docker/Podman is required for Lambda execution but is not available",
1112 )
1113 })?;
1114
1115 match invocation_type {
1116 InvocationType::Event => {
1117 let runtime = runtime.clone();
1119 let func_clone = func.clone();
1120 let payload_vec = payload.to_vec();
1121 let bus = self.delivery_bus.clone();
1122 let destination_config = self.lookup_destination_config(&func, account_id);
1123 let function_arn = func.function_arn.clone();
1124 let layer_zips_async = layer_zips.clone();
1125 tokio::spawn(async move {
1126 let result = match runtime
1127 .invoke(&func_clone, &payload_vec, &layer_zips_async)
1128 .await
1129 {
1130 Ok(bytes) => {
1131 let parsed: Option<serde_json::Value> =
1135 serde_json::from_slice(&bytes).ok();
1136 let is_error = parsed
1137 .as_ref()
1138 .and_then(|v| v.as_object())
1139 .map(|m| {
1140 m.contains_key("errorMessage") || m.contains_key("errorType")
1141 })
1142 .unwrap_or(false);
1143 if is_error {
1144 let msg = parsed
1145 .as_ref()
1146 .and_then(|v| v.get("errorMessage"))
1147 .and_then(|v| v.as_str())
1148 .unwrap_or("function error")
1149 .to_string();
1150 Err(msg)
1151 } else {
1152 Ok(bytes)
1153 }
1154 }
1155 Err(e) => Err(e.to_string()),
1156 };
1157 if let Some(bus) = bus {
1158 route_to_destination(
1159 bus,
1160 &function_arn,
1161 &payload_vec,
1162 &result,
1163 destination_config.as_ref(),
1164 );
1165 }
1166 });
1167 let mut resp = AwsResponse::json(StatusCode::ACCEPTED, "");
1168 resp.headers.insert(
1169 http::header::HeaderName::from_static("x-amz-executed-version"),
1170 http::header::HeaderValue::from_static("$LATEST"),
1171 );
1172 Ok(resp)
1173 }
1174 InvocationType::RequestResponse | InvocationType::DryRun => {
1175 match runtime.invoke(&func, payload, &layer_zips).await {
1176 Ok(response_bytes) => {
1177 let mut resp = AwsResponse::json(StatusCode::OK, response_bytes);
1178 resp.headers.insert(
1179 http::header::HeaderName::from_static("x-amz-executed-version"),
1180 http::header::HeaderValue::from_static("$LATEST"),
1181 );
1182 Ok(resp)
1183 }
1184 Err(e) => {
1185 tracing::error!(function = %function_name, error = %e, "Lambda invocation failed");
1186 Err(AwsServiceError::aws_error(
1187 StatusCode::INTERNAL_SERVER_ERROR,
1188 "ServiceException",
1189 format!("Lambda execution failed: {e}"),
1190 ))
1191 }
1192 }
1193 }
1194 }
1195 }
1196
1197 fn lookup_destination_config(
1202 &self,
1203 func: &crate::state::LambdaFunction,
1204 account_id: &str,
1205 ) -> Option<serde_json::Value> {
1206 let accounts = self.state.read();
1207 let state = accounts.get(account_id)?;
1208 let key = format!("{}:$LATEST", func.function_name);
1209 state
1210 .event_invoke_configs
1211 .get(&key)
1212 .map(|cfg| cfg.destination_config.clone())
1213 .filter(|v| !v.is_null() && !v.as_object().map(|o| o.is_empty()).unwrap_or(false))
1214 }
1215
1216 fn publish_version(
1217 &self,
1218 function_name: &str,
1219 account_id: &str,
1220 ) -> Result<AwsResponse, AwsServiceError> {
1221 let accounts = self.state.read();
1222 let empty = LambdaState::new(account_id, "");
1223 let state = accounts.get(account_id).unwrap_or(&empty);
1224 let func = state.functions.get(function_name).ok_or_else(|| {
1225 AwsServiceError::aws_error(
1226 StatusCode::NOT_FOUND,
1227 "ResourceNotFoundException",
1228 format!(
1229 "Function not found: arn:aws:lambda:{}:{}:function:{}",
1230 state.region, state.account_id, function_name
1231 ),
1232 )
1233 })?;
1234
1235 let mut config = self.function_config_json(func);
1236 config["Version"] = json!("1");
1238 config["FunctionArn"] = json!(format!("{}:1", func.function_arn));
1239
1240 Ok(AwsResponse::json(StatusCode::CREATED, config.to_string()))
1241 }
1242
1243 fn create_event_source_mapping(
1244 &self,
1245 req: &AwsRequest,
1246 ) -> Result<AwsResponse, AwsServiceError> {
1247 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1248 let event_source_arn = body["EventSourceArn"]
1249 .as_str()
1250 .ok_or_else(|| {
1251 AwsServiceError::aws_error(
1252 StatusCode::BAD_REQUEST,
1253 "InvalidParameterValueException",
1254 "EventSourceArn is required",
1255 )
1256 })?
1257 .to_string();
1258
1259 let function_name = body["FunctionName"]
1260 .as_str()
1261 .ok_or_else(|| {
1262 AwsServiceError::aws_error(
1263 StatusCode::BAD_REQUEST,
1264 "InvalidParameterValueException",
1265 "FunctionName is required",
1266 )
1267 })?
1268 .to_string();
1269
1270 let mut accounts = self.state.write();
1271 let state = accounts.get_or_create(&req.account_id);
1272
1273 let function_arn = if function_name.starts_with("arn:") {
1275 function_name.clone()
1276 } else {
1277 let func = state.functions.get(&function_name).ok_or_else(|| {
1278 AwsServiceError::aws_error(
1279 StatusCode::NOT_FOUND,
1280 "ResourceNotFoundException",
1281 format!(
1282 "Function not found: arn:aws:lambda:{}:{}:function:{}",
1283 state.region, state.account_id, function_name
1284 ),
1285 )
1286 })?;
1287 func.function_arn.clone()
1288 };
1289
1290 let batch_size = body["BatchSize"].as_i64().unwrap_or(10);
1291 let enabled = body["Enabled"].as_bool().unwrap_or(true);
1292 let mapping_uuid = uuid::Uuid::new_v4().to_string();
1293 let now = Utc::now();
1294
1295 let filter_patterns: Vec<String> = match body.get("FilterCriteria") {
1303 None | Some(Value::Null) => Vec::new(),
1304 Some(Value::Object(_)) => {
1305 match body.get("FilterCriteria").and_then(|v| v.get("Filters")) {
1306 None => Vec::new(),
1307 Some(Value::Array(arr)) => {
1308 let mut out = Vec::with_capacity(arr.len());
1309 for f in arr {
1310 match f.get("Pattern") {
1311 Some(Value::String(s)) => out.push(s.clone()),
1312 _ => {
1313 return Err(AwsServiceError::aws_error(
1314 StatusCode::BAD_REQUEST,
1315 "InvalidParameterValueException",
1316 "FilterCriteria.Filters[].Pattern must be a string",
1317 ));
1318 }
1319 }
1320 }
1321 out
1322 }
1323 Some(_) => {
1324 return Err(AwsServiceError::aws_error(
1325 StatusCode::BAD_REQUEST,
1326 "InvalidParameterValueException",
1327 "FilterCriteria.Filters must be an array",
1328 ));
1329 }
1330 }
1331 }
1332 Some(_) => {
1333 return Err(AwsServiceError::aws_error(
1334 StatusCode::BAD_REQUEST,
1335 "InvalidParameterValueException",
1336 "FilterCriteria must be an object",
1337 ));
1338 }
1339 };
1340 if let Err(err) = crate::filter::FilterSet::validate(filter_patterns.iter()) {
1342 return Err(AwsServiceError::aws_error(
1343 StatusCode::BAD_REQUEST,
1344 "InvalidParameterValueException",
1345 err,
1346 ));
1347 }
1348 let function_response_types: Vec<String> = body
1349 .get("FunctionResponseTypes")
1350 .and_then(|v| v.as_array())
1351 .map(|arr| {
1352 arr.iter()
1353 .filter_map(|v| v.as_str().map(String::from))
1354 .collect()
1355 })
1356 .unwrap_or_default();
1357 let starting_position = body
1358 .get("StartingPosition")
1359 .and_then(|v| v.as_str())
1360 .map(String::from);
1361 let starting_position_timestamp = body
1362 .get("StartingPositionTimestamp")
1363 .and_then(|v| v.as_f64());
1364 let parallelization_factor = body.get("ParallelizationFactor").and_then(|v| v.as_i64());
1365 let maximum_batching_window_in_seconds = body
1366 .get("MaximumBatchingWindowInSeconds")
1367 .and_then(|v| v.as_i64());
1368
1369 let mapping = EventSourceMapping {
1370 uuid: mapping_uuid.clone(),
1371 function_arn: function_arn.clone(),
1372 event_source_arn: event_source_arn.clone(),
1373 batch_size,
1374 enabled,
1375 state: if enabled {
1376 "Enabled".to_string()
1377 } else {
1378 "Disabled".to_string()
1379 },
1380 last_modified: now,
1381 filter_patterns,
1382 maximum_batching_window_in_seconds,
1383 starting_position,
1384 starting_position_timestamp,
1385 parallelization_factor,
1386 function_response_types,
1387 };
1388
1389 let response = self.event_source_mapping_json(&mapping);
1390 state.event_source_mappings.insert(mapping_uuid, mapping);
1391
1392 Ok(AwsResponse::json(
1393 StatusCode::ACCEPTED,
1394 response.to_string(),
1395 ))
1396 }
1397
1398 fn list_event_source_mappings(&self, account_id: &str) -> Result<AwsResponse, AwsServiceError> {
1399 let accounts = self.state.read();
1400 let empty = LambdaState::new(account_id, "");
1401 let state = accounts.get(account_id).unwrap_or(&empty);
1402 let mappings: Vec<Value> = state
1403 .event_source_mappings
1404 .values()
1405 .map(|m| self.event_source_mapping_json(m))
1406 .collect();
1407
1408 let response = json!({
1409 "EventSourceMappings": mappings,
1410 });
1411
1412 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1413 }
1414
1415 fn get_event_source_mapping(
1416 &self,
1417 uuid: &str,
1418 account_id: &str,
1419 ) -> Result<AwsResponse, AwsServiceError> {
1420 let accounts = self.state.read();
1421 let empty = LambdaState::new(account_id, "");
1422 let state = accounts.get(account_id).unwrap_or(&empty);
1423 let mapping = state.event_source_mappings.get(uuid).ok_or_else(|| {
1424 AwsServiceError::aws_error(
1425 StatusCode::NOT_FOUND,
1426 "ResourceNotFoundException",
1427 format!("The resource you requested does not exist. (Service: Lambda, Status Code: 404, Request ID: {uuid})"),
1428 )
1429 })?;
1430
1431 let response = self.event_source_mapping_json(mapping);
1432 Ok(AwsResponse::json(StatusCode::OK, response.to_string()))
1433 }
1434
1435 fn delete_event_source_mapping(
1436 &self,
1437 uuid: &str,
1438 account_id: &str,
1439 ) -> Result<AwsResponse, AwsServiceError> {
1440 let mut accounts = self.state.write();
1441 let state = accounts.get_or_create(account_id);
1442 let mapping = state.event_source_mappings.remove(uuid).ok_or_else(|| {
1443 AwsServiceError::aws_error(
1444 StatusCode::NOT_FOUND,
1445 "ResourceNotFoundException",
1446 format!("The resource you requested does not exist. (Service: Lambda, Status Code: 404, Request ID: {uuid})"),
1447 )
1448 })?;
1449
1450 let mut response = self.event_source_mapping_json(&mapping);
1451 response["State"] = json!("Deleting");
1452 Ok(AwsResponse::json(
1453 StatusCode::ACCEPTED,
1454 response.to_string(),
1455 ))
1456 }
1457
1458 pub(crate) fn function_config_json(&self, func: &LambdaFunction) -> Value {
1459 let mut env_vars = json!({});
1460 if !func.environment.is_empty() {
1461 env_vars = json!({ "Variables": func.environment });
1462 }
1463
1464 let mut config = json!({
1465 "FunctionName": func.function_name,
1466 "FunctionArn": func.function_arn,
1467 "Runtime": func.runtime,
1468 "Role": func.role,
1469 "Handler": func.handler,
1470 "Description": func.description,
1471 "Timeout": func.timeout,
1472 "MemorySize": func.memory_size,
1473 "CodeSha256": func.code_sha256,
1474 "CodeSize": func.code_size,
1475 "Version": func.version,
1476 "LastModified": func.last_modified.format("%Y-%m-%dT%H:%M:%S%.3f+0000").to_string(),
1477 "PackageType": func.package_type,
1478 "Architectures": func.architectures,
1479 "Environment": env_vars,
1480 "State": "Active",
1481 "LastUpdateStatus": "Successful",
1482 "TracingConfig": { "Mode": "PassThrough" },
1483 "RevisionId": uuid::Uuid::new_v4().to_string(),
1484 });
1485 if let Some(ref uri) = func.image_uri {
1486 config["Code"] = json!({
1487 "ImageUri": uri,
1488 "ResolvedImageUri": uri,
1489 });
1490 }
1491 if !func.layers.is_empty() {
1492 config["Layers"] = json!(func
1493 .layers
1494 .iter()
1495 .map(|l| json!({"Arn": l.arn, "CodeSize": l.code_size}))
1496 .collect::<Vec<_>>());
1497 }
1498 config
1499 }
1500
1501 fn event_source_mapping_json(&self, mapping: &EventSourceMapping) -> Value {
1502 let mut out = json!({
1503 "UUID": mapping.uuid,
1504 "FunctionArn": mapping.function_arn,
1505 "EventSourceArn": mapping.event_source_arn,
1506 "BatchSize": mapping.batch_size,
1507 "State": mapping.state,
1508 "LastModified": mapping.last_modified.timestamp_millis() as f64 / 1000.0,
1509 });
1510 let obj = out.as_object_mut().expect("json! built object");
1511 if !mapping.filter_patterns.is_empty() {
1512 obj.insert(
1513 "FilterCriteria".into(),
1514 json!({
1515 "Filters": mapping.filter_patterns.iter().map(|p| json!({"Pattern": p})).collect::<Vec<_>>(),
1516 }),
1517 );
1518 }
1519 if !mapping.function_response_types.is_empty() {
1520 obj.insert(
1521 "FunctionResponseTypes".into(),
1522 json!(mapping.function_response_types),
1523 );
1524 }
1525 if let Some(sp) = &mapping.starting_position {
1526 obj.insert("StartingPosition".into(), json!(sp));
1527 }
1528 if let Some(ts) = mapping.starting_position_timestamp {
1529 obj.insert("StartingPositionTimestamp".into(), json!(ts));
1530 }
1531 if let Some(pf) = mapping.parallelization_factor {
1532 obj.insert("ParallelizationFactor".into(), json!(pf));
1533 }
1534 if let Some(w) = mapping.maximum_batching_window_in_seconds {
1535 obj.insert("MaximumBatchingWindowInSeconds".into(), json!(w));
1536 }
1537 out
1538 }
1539
1540 fn add_permission(
1553 &self,
1554 function_name: &str,
1555 req: &AwsRequest,
1556 ) -> Result<AwsResponse, AwsServiceError> {
1557 let body: Value = serde_json::from_slice(&req.body).unwrap_or_default();
1558 let statement_id = body
1559 .get("StatementId")
1560 .and_then(|v| v.as_str())
1561 .ok_or_else(|| {
1562 AwsServiceError::aws_error(
1563 StatusCode::BAD_REQUEST,
1564 "InvalidParameterValueException",
1565 "StatementId is required",
1566 )
1567 })?
1568 .to_string();
1569 let action = body
1570 .get("Action")
1571 .and_then(|v| v.as_str())
1572 .ok_or_else(|| {
1573 AwsServiceError::aws_error(
1574 StatusCode::BAD_REQUEST,
1575 "InvalidParameterValueException",
1576 "Action is required",
1577 )
1578 })?
1579 .to_string();
1580 let principal_raw = body
1581 .get("Principal")
1582 .and_then(|v| v.as_str())
1583 .ok_or_else(|| {
1584 AwsServiceError::aws_error(
1585 StatusCode::BAD_REQUEST,
1586 "InvalidParameterValueException",
1587 "Principal is required",
1588 )
1589 })?
1590 .to_string();
1591 let source_arn = body
1592 .get("SourceArn")
1593 .and_then(|v| v.as_str())
1594 .map(str::to_string);
1595 let source_account = body
1596 .get("SourceAccount")
1597 .and_then(|v| v.as_str())
1598 .map(str::to_string);
1599
1600 let mut accounts = self.state.write();
1601 let state = accounts.get_or_create(&req.account_id);
1602 let func = state.functions.get_mut(function_name).ok_or_else(|| {
1603 AwsServiceError::aws_error(
1604 StatusCode::NOT_FOUND,
1605 "ResourceNotFoundException",
1606 format!("Function not found: {function_name}"),
1607 )
1608 })?;
1609
1610 let mut doc: Value = func
1618 .policy
1619 .as_deref()
1620 .and_then(|s| serde_json::from_str::<Value>(s).ok())
1621 .filter(|v| v.is_object())
1622 .unwrap_or_else(|| json!({"Version": "2012-10-17", "Statement": []}));
1623
1624 if !doc.get("Statement").map(|s| s.is_array()).unwrap_or(false) {
1626 doc["Statement"] = json!([]);
1627 }
1628 let statements = doc["Statement"].as_array_mut().unwrap();
1629
1630 if statements
1633 .iter()
1634 .any(|s| s.get("Sid").and_then(|v| v.as_str()) == Some(statement_id.as_str()))
1635 {
1636 return Err(AwsServiceError::aws_error(
1637 StatusCode::CONFLICT,
1638 "ResourceConflictException",
1639 format!("The statement id ({statement_id}) provided already exists"),
1640 ));
1641 }
1642
1643 let principal_value =
1650 if principal_raw.ends_with(".amazonaws.com") || principal_raw.contains(".amazon") {
1651 json!({ "Service": principal_raw })
1652 } else {
1653 json!({ "AWS": principal_raw })
1654 };
1655
1656 let mut condition = serde_json::Map::new();
1660 if let Some(arn) = source_arn.as_ref() {
1661 condition.insert("ArnLike".to_string(), json!({ "aws:SourceArn": arn }));
1662 }
1663 if let Some(acct) = source_account.as_ref() {
1664 condition.insert(
1665 "StringEquals".to_string(),
1666 json!({ "aws:SourceAccount": acct }),
1667 );
1668 }
1669
1670 let mut new_statement = serde_json::Map::new();
1671 new_statement.insert("Sid".to_string(), json!(statement_id));
1672 new_statement.insert("Effect".to_string(), json!("Allow"));
1673 new_statement.insert("Principal".to_string(), principal_value);
1674 new_statement.insert("Action".to_string(), json!(format!("lambda:{action}")));
1675 new_statement.insert("Resource".to_string(), json!(func.function_arn));
1676 if !condition.is_empty() {
1677 new_statement.insert("Condition".to_string(), Value::Object(condition));
1678 }
1679 let statement_json = Value::Object(new_statement);
1680 statements.push(statement_json.clone());
1681
1682 func.policy = Some(serde_json::to_string(&doc).unwrap());
1683
1684 Ok(AwsResponse::json(
1685 StatusCode::CREATED,
1686 json!({ "Statement": serde_json::to_string(&statement_json).unwrap() }).to_string(),
1687 ))
1688 }
1689
1690 fn remove_permission(
1691 &self,
1692 function_name: &str,
1693 statement_id: &str,
1694 account_id: &str,
1695 ) -> Result<AwsResponse, AwsServiceError> {
1696 let mut accounts = self.state.write();
1697 let state = accounts.get_or_create(account_id);
1698 let func = state.functions.get_mut(function_name).ok_or_else(|| {
1699 AwsServiceError::aws_error(
1700 StatusCode::NOT_FOUND,
1701 "ResourceNotFoundException",
1702 format!("Function not found: {function_name}"),
1703 )
1704 })?;
1705 let policy_str = func.policy.as_deref().ok_or_else(|| {
1706 AwsServiceError::aws_error(
1707 StatusCode::NOT_FOUND,
1708 "ResourceNotFoundException",
1709 format!("No policy is associated with function {function_name}"),
1710 )
1711 })?;
1712 let mut doc: Value = serde_json::from_str(policy_str).map_err(|_| {
1713 AwsServiceError::aws_error(
1714 StatusCode::INTERNAL_SERVER_ERROR,
1715 "InternalError",
1716 "stored resource policy is not valid JSON",
1717 )
1718 })?;
1719 let statements = doc
1720 .get_mut("Statement")
1721 .and_then(|s| s.as_array_mut())
1722 .ok_or_else(|| {
1723 AwsServiceError::aws_error(
1724 StatusCode::INTERNAL_SERVER_ERROR,
1725 "InternalError",
1726 "stored resource policy has no Statement array",
1727 )
1728 })?;
1729 let before = statements.len();
1730 statements.retain(|s| s.get("Sid").and_then(|v| v.as_str()) != Some(statement_id));
1731 if statements.len() == before {
1732 return Err(AwsServiceError::aws_error(
1733 StatusCode::NOT_FOUND,
1734 "ResourceNotFoundException",
1735 format!("Statement {statement_id} is not found in resource policy"),
1736 ));
1737 }
1738 func.policy = Some(serde_json::to_string(&doc).unwrap());
1742 Ok(AwsResponse::json(StatusCode::NO_CONTENT, String::new()))
1743 }
1744
1745 fn get_policy(
1746 &self,
1747 function_name: &str,
1748 account_id: &str,
1749 ) -> Result<AwsResponse, AwsServiceError> {
1750 let accounts = self.state.read();
1751 let empty = LambdaState::new(account_id, "");
1752 let state = accounts.get(account_id).unwrap_or(&empty);
1753 let func = state.functions.get(function_name).ok_or_else(|| {
1754 AwsServiceError::aws_error(
1755 StatusCode::NOT_FOUND,
1756 "ResourceNotFoundException",
1757 format!("Function not found: {function_name}"),
1758 )
1759 })?;
1760 let policy = func.policy.as_deref().ok_or_else(|| {
1761 AwsServiceError::aws_error(
1762 StatusCode::NOT_FOUND,
1763 "ResourceNotFoundException",
1764 format!("No policy is associated with function {function_name}"),
1765 )
1766 })?;
1767 Ok(AwsResponse::json(
1768 StatusCode::OK,
1769 json!({
1770 "Policy": policy,
1771 "RevisionId": uuid::Uuid::new_v4().to_string(),
1772 })
1773 .to_string(),
1774 ))
1775 }
1776}
1777
1778#[async_trait]
1779impl AwsService for LambdaService {
1780 fn service_name(&self) -> &str {
1781 "lambda"
1782 }
1783
1784 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1785 let (action, resource_name) = Self::resolve_action(&req).ok_or_else(|| {
1786 AwsServiceError::aws_error(
1787 StatusCode::NOT_FOUND,
1788 "UnknownOperationException",
1789 format!("Unknown operation: {} {}", req.method, req.raw_path),
1790 )
1791 })?;
1792
1793 let resource_name = if action_takes_function_name(action) {
1798 resource_name.map(|s| normalize_function_name(&s))
1799 } else {
1800 resource_name
1801 };
1802
1803 let mutates = matches!(
1804 action,
1805 "CreateFunction"
1806 | "DeleteFunction"
1807 | "PublishVersion"
1808 | "AddPermission"
1809 | "RemovePermission"
1810 | "CreateEventSourceMapping"
1811 | "DeleteEventSourceMapping"
1812 | "UpdateEventSourceMapping"
1813 | "UpdateFunctionCode"
1814 | "UpdateFunctionConfiguration"
1815 | "CreateAlias"
1816 | "DeleteAlias"
1817 | "UpdateAlias"
1818 | "PublishLayerVersion"
1819 | "DeleteLayerVersion"
1820 | "AddLayerVersionPermission"
1821 | "RemoveLayerVersionPermission"
1822 | "CreateFunctionUrlConfig"
1823 | "DeleteFunctionUrlConfig"
1824 | "UpdateFunctionUrlConfig"
1825 | "PutFunctionConcurrency"
1826 | "DeleteFunctionConcurrency"
1827 | "PutProvisionedConcurrencyConfig"
1828 | "DeleteProvisionedConcurrencyConfig"
1829 | "CreateCodeSigningConfig"
1830 | "UpdateCodeSigningConfig"
1831 | "DeleteCodeSigningConfig"
1832 | "PutFunctionCodeSigningConfig"
1833 | "DeleteFunctionCodeSigningConfig"
1834 | "PutFunctionEventInvokeConfig"
1835 | "UpdateFunctionEventInvokeConfig"
1836 | "DeleteFunctionEventInvokeConfig"
1837 | "PutRuntimeManagementConfig"
1838 | "PutFunctionScalingConfig"
1839 | "PutFunctionRecursionConfig"
1840 | "TagResource"
1841 | "UntagResource"
1842 | "CreateCapacityProvider"
1843 | "UpdateCapacityProvider"
1844 | "DeleteCapacityProvider"
1845 | "CheckpointDurableExecution"
1846 | "StopDurableExecution"
1847 | "SendDurableExecutionCallbackSuccess"
1848 | "SendDurableExecutionCallbackFailure"
1849 | "SendDurableExecutionCallbackHeartbeat"
1850 | "InvokeAsync"
1851 | "InvokeWithResponseStream"
1852 );
1853
1854 let aid = &req.account_id;
1855 let result = match action {
1856 "CreateFunction" => self.create_function(&req),
1857 "ListFunctions" => self.list_functions(aid),
1858 "GetFunction" => self.get_function(
1859 resource_name.as_deref().unwrap_or(""),
1860 aid,
1861 req.region.as_str(),
1862 ),
1863 "DeleteFunction" => self.delete_function(resource_name.as_deref().unwrap_or(""), aid),
1864 "Invoke" => {
1865 let invocation_type = InvocationType::from_header(
1866 req.headers
1867 .get("x-amz-invocation-type")
1868 .and_then(|v| v.to_str().ok()),
1869 );
1870 self.invoke(
1871 resource_name.as_deref().unwrap_or(""),
1872 &req.body,
1873 aid,
1874 invocation_type,
1875 )
1876 .await
1877 }
1878 "InvokeAsync" => {
1879 self.invoke(
1880 resource_name.as_deref().unwrap_or(""),
1881 &req.body,
1882 aid,
1883 InvocationType::Event,
1884 )
1885 .await
1886 }
1887 "PublishVersion" => self.publish_version(resource_name.as_deref().unwrap_or(""), aid),
1888 "AddPermission" => self.add_permission(resource_name.as_deref().unwrap_or(""), &req),
1889 "GetPolicy" => self.get_policy(resource_name.as_deref().unwrap_or(""), aid),
1890 "RemovePermission" => {
1891 let sid = req.path_segments.get(4).cloned().unwrap_or_default();
1893 self.remove_permission(resource_name.as_deref().unwrap_or(""), &sid, aid)
1894 }
1895 "CreateEventSourceMapping" => self.create_event_source_mapping(&req),
1896 "ListEventSourceMappings" => self.list_event_source_mappings(aid),
1897 "GetEventSourceMapping" => {
1898 self.get_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1899 }
1900 "DeleteEventSourceMapping" => {
1901 self.delete_event_source_mapping(resource_name.as_deref().unwrap_or(""), aid)
1902 }
1903 other => {
1904 self.handle_extra(other, resource_name.as_deref(), &req)
1905 .await
1906 }
1907 };
1908 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
1909 self.save_snapshot().await;
1910 }
1911 result
1912 }
1913
1914 fn supported_actions(&self) -> &[&str] {
1915 &[
1916 "CreateFunction",
1917 "GetFunction",
1918 "DeleteFunction",
1919 "ListFunctions",
1920 "Invoke",
1921 "InvokeAsync",
1922 "InvokeWithResponseStream",
1923 "PublishVersion",
1924 "ListVersionsByFunction",
1925 "AddPermission",
1926 "RemovePermission",
1927 "GetPolicy",
1928 "CreateEventSourceMapping",
1929 "ListEventSourceMappings",
1930 "GetEventSourceMapping",
1931 "UpdateEventSourceMapping",
1932 "DeleteEventSourceMapping",
1933 "GetFunctionConfiguration",
1934 "UpdateFunctionConfiguration",
1935 "UpdateFunctionCode",
1936 "GetAccountSettings",
1937 "CreateAlias",
1938 "GetAlias",
1939 "ListAliases",
1940 "UpdateAlias",
1941 "DeleteAlias",
1942 "PublishLayerVersion",
1943 "GetLayerVersion",
1944 "GetLayerVersionByArn",
1945 "DeleteLayerVersion",
1946 "ListLayerVersions",
1947 "ListLayers",
1948 "GetLayerVersionPolicy",
1949 "AddLayerVersionPermission",
1950 "RemoveLayerVersionPermission",
1951 "CreateFunctionUrlConfig",
1952 "GetFunctionUrlConfig",
1953 "UpdateFunctionUrlConfig",
1954 "DeleteFunctionUrlConfig",
1955 "ListFunctionUrlConfigs",
1956 "PutFunctionConcurrency",
1957 "GetFunctionConcurrency",
1958 "DeleteFunctionConcurrency",
1959 "PutProvisionedConcurrencyConfig",
1960 "GetProvisionedConcurrencyConfig",
1961 "DeleteProvisionedConcurrencyConfig",
1962 "ListProvisionedConcurrencyConfigs",
1963 "CreateCodeSigningConfig",
1964 "GetCodeSigningConfig",
1965 "UpdateCodeSigningConfig",
1966 "DeleteCodeSigningConfig",
1967 "ListCodeSigningConfigs",
1968 "PutFunctionCodeSigningConfig",
1969 "GetFunctionCodeSigningConfig",
1970 "DeleteFunctionCodeSigningConfig",
1971 "ListFunctionsByCodeSigningConfig",
1972 "PutFunctionEventInvokeConfig",
1973 "GetFunctionEventInvokeConfig",
1974 "UpdateFunctionEventInvokeConfig",
1975 "DeleteFunctionEventInvokeConfig",
1976 "ListFunctionEventInvokeConfigs",
1977 "PutRuntimeManagementConfig",
1978 "GetRuntimeManagementConfig",
1979 "PutFunctionScalingConfig",
1980 "GetFunctionScalingConfig",
1981 "PutFunctionRecursionConfig",
1982 "GetFunctionRecursionConfig",
1983 "TagResource",
1984 "UntagResource",
1985 "ListTags",
1986 "CreateCapacityProvider",
1987 "GetCapacityProvider",
1988 "UpdateCapacityProvider",
1989 "DeleteCapacityProvider",
1990 "ListCapacityProviders",
1991 "ListFunctionVersionsByCapacityProvider",
1992 "CheckpointDurableExecution",
1993 "GetDurableExecution",
1994 "GetDurableExecutionHistory",
1995 "GetDurableExecutionState",
1996 "ListDurableExecutionsByFunction",
1997 "StopDurableExecution",
1998 "SendDurableExecutionCallbackSuccess",
1999 "SendDurableExecutionCallbackFailure",
2000 "SendDurableExecutionCallbackHeartbeat",
2001 ]
2002 }
2003
2004 fn iam_enforceable(&self) -> bool {
2005 true
2006 }
2007
2008 fn iam_action_for(&self, request: &AwsRequest) -> Option<fakecloud_core::auth::IamAction> {
2012 let (action_str, resource_name) = Self::resolve_action(request)?;
2017 let action: &'static str = match action_str {
2018 "CreateFunction" => "CreateFunction",
2019 "ListFunctions" => "ListFunctions",
2020 "GetFunction" => "GetFunction",
2021 "DeleteFunction" => "DeleteFunction",
2022 "Invoke" => "InvokeFunction",
2023 "PublishVersion" => "PublishVersion",
2024 "AddPermission" => "AddPermission",
2025 "RemovePermission" => "RemovePermission",
2026 "GetPolicy" => "GetPolicy",
2027 "CreateEventSourceMapping" => "CreateEventSourceMapping",
2028 "ListEventSourceMappings" => "ListEventSourceMappings",
2029 "GetEventSourceMapping" => "GetEventSourceMapping",
2030 "DeleteEventSourceMapping" => "DeleteEventSourceMapping",
2031 _ => return None,
2032 };
2033 let accounts = self.state.read();
2034 let empty = LambdaState::new(&request.account_id, &request.region);
2035 let state = accounts.get(&request.account_id).unwrap_or(&empty);
2036 let resource = match action {
2037 "GetFunction" | "DeleteFunction" | "InvokeFunction" | "PublishVersion"
2038 | "AddPermission" | "RemovePermission" | "GetPolicy" => {
2039 let name = resource_name.unwrap_or_default();
2040 if name.is_empty() {
2041 "*".to_string()
2042 } else {
2043 format!(
2044 "arn:aws:lambda:{}:{}:function:{}",
2045 state.region, state.account_id, name
2046 )
2047 }
2048 }
2049 "CreateFunction" => {
2050 serde_json::from_slice::<Value>(&request.body)
2055 .ok()
2056 .and_then(|v| {
2057 v.get("FunctionName").and_then(|f| f.as_str()).map(|n| {
2058 format!(
2059 "arn:aws:lambda:{}:{}:function:{}",
2060 state.region, state.account_id, n
2061 )
2062 })
2063 })
2064 .unwrap_or_else(|| "*".to_string())
2065 }
2066 _ => "*".to_string(),
2067 };
2068 Some(fakecloud_core::auth::IamAction {
2069 service: "lambda",
2070 action,
2071 resource,
2072 })
2073 }
2074
2075 fn iam_condition_keys_for(
2076 &self,
2077 request: &AwsRequest,
2078 action: &fakecloud_core::auth::IamAction,
2079 ) -> std::collections::BTreeMap<String, Vec<String>> {
2080 let mut out = std::collections::BTreeMap::new();
2081 if action.action == "AddPermission" {
2082 if action.resource != "*" {
2083 out.insert(
2084 "lambda:functionarn".to_string(),
2085 vec![action.resource.clone()],
2086 );
2087 }
2088 if let Ok(body) = serde_json::from_slice::<Value>(&request.body) {
2089 if let Some(principal) = body.get("Principal").and_then(|p| p.as_str()) {
2090 out.insert("lambda:principal".to_string(), vec![principal.to_string()]);
2091 }
2092 }
2093 }
2094 out
2095 }
2096}
2097
2098#[cfg(test)]
2099mod tests {
2100 use super::*;
2101 use bytes::Bytes;
2102 use http::{HeaderMap, Method};
2103 use parking_lot::RwLock;
2104 use std::collections::HashMap;
2105 use std::sync::Arc;
2106
2107 fn make_state() -> SharedLambdaState {
2108 Arc::new(RwLock::new(
2109 fakecloud_core::multi_account::MultiAccountState::new("123456789012", "us-east-1", ""),
2110 ))
2111 }
2112
2113 fn make_request(method: Method, path: &str, body: &str) -> AwsRequest {
2114 let path_segments: Vec<String> = path
2115 .split('/')
2116 .filter(|s| !s.is_empty())
2117 .map(|s| s.to_string())
2118 .collect();
2119 AwsRequest {
2120 service: "lambda".to_string(),
2121 action: String::new(),
2122 region: "us-east-1".to_string(),
2123 account_id: "123456789012".to_string(),
2124 request_id: "test-request-id".to_string(),
2125 headers: HeaderMap::new(),
2126 query_params: HashMap::new(),
2127 body: Bytes::from(body.to_string()),
2128 body_stream: parking_lot::Mutex::new(None),
2129 path_segments,
2130 raw_path: path.to_string(),
2131 raw_query: String::new(),
2132 method,
2133 is_query_protocol: false,
2134 access_key_id: None,
2135 principal: None,
2136 }
2137 }
2138
2139 #[test]
2140 fn normalize_function_name_bare_name_passes_through() {
2141 assert_eq!(normalize_function_name("MyFunction"), "MyFunction");
2142 }
2143
2144 #[test]
2145 fn normalize_function_name_strips_qualifier_from_bare_name() {
2146 assert_eq!(normalize_function_name("MyFunction:PROD"), "MyFunction");
2147 assert_eq!(normalize_function_name("MyFunction:1"), "MyFunction");
2148 }
2149
2150 #[test]
2151 fn normalize_function_name_strips_full_arn() {
2152 assert_eq!(
2153 normalize_function_name("arn:aws:lambda:us-east-1:123456789012:function:MyFunction"),
2154 "MyFunction"
2155 );
2156 }
2157
2158 #[test]
2159 fn normalize_function_name_strips_qualified_full_arn() {
2160 assert_eq!(
2161 normalize_function_name(
2162 "arn:aws:lambda:us-east-1:123456789012:function:MyFunction:PROD"
2163 ),
2164 "MyFunction"
2165 );
2166 }
2167
2168 #[test]
2169 fn normalize_function_name_strips_partial_arn() {
2170 assert_eq!(
2171 normalize_function_name("123456789012:function:MyFunction"),
2172 "MyFunction"
2173 );
2174 assert_eq!(
2175 normalize_function_name("123456789012:function:MyFunction:1"),
2176 "MyFunction"
2177 );
2178 }
2179
2180 #[test]
2181 fn normalize_function_name_leaves_malformed_arn_alone() {
2182 let s = "arn:aws:s3:us-east-1:123456789012:function:Foo";
2184 assert_eq!(normalize_function_name(s), s);
2185 let s2 = "abc:function:Foo";
2187 assert_eq!(normalize_function_name(s2), s2);
2188 }
2189
2190 #[test]
2191 fn normalize_function_name_empty() {
2192 assert_eq!(normalize_function_name(""), "");
2193 }
2194
2195 #[test]
2196 fn normalize_function_name_decodes_percent_encoded_arn() {
2197 let encoded = "arn%3Aaws%3Alambda%3Aus-east-1%3A123456789012%3Afunction%3AMyFunc";
2200 assert_eq!(normalize_function_name(encoded), "MyFunc");
2201 }
2202
2203 #[tokio::test]
2204 async fn get_function_accepts_full_arn() {
2205 let svc = LambdaService::new(make_state());
2206 let create_body = json!({
2208 "FunctionName": "MyFunc",
2209 "Runtime": "nodejs20.x",
2210 "Role": "arn:aws:iam::123456789012:role/lambda-role",
2211 "Handler": "index.handler",
2212 "Code": {"ZipFile": ""},
2213 })
2214 .to_string();
2215 let req = make_request(Method::POST, "/2015-03-31/functions", &create_body);
2216 svc.handle(req).await.expect("create function");
2217
2218 let req = make_request(
2220 Method::GET,
2221 "/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:MyFunc",
2222 "",
2223 );
2224 let resp = svc.handle(req).await.expect("get function by ARN");
2225 assert_eq!(resp.status, StatusCode::OK);
2226 }
2227
2228 #[tokio::test]
2229 async fn get_function_accepts_partial_arn() {
2230 let svc = LambdaService::new(make_state());
2231 let create_body = json!({
2232 "FunctionName": "MyFunc",
2233 "Runtime": "nodejs20.x",
2234 "Role": "arn:aws:iam::123456789012:role/lambda-role",
2235 "Handler": "index.handler",
2236 "Code": {"ZipFile": ""},
2237 })
2238 .to_string();
2239 let req = make_request(Method::POST, "/2015-03-31/functions", &create_body);
2240 svc.handle(req).await.expect("create function");
2241
2242 let req = make_request(
2243 Method::GET,
2244 "/2015-03-31/functions/123456789012:function:MyFunc",
2245 "",
2246 );
2247 let resp = svc.handle(req).await.expect("get function by partial ARN");
2248 assert_eq!(resp.status, StatusCode::OK);
2249 }
2250
2251 #[tokio::test]
2252 async fn get_function_accepts_name_with_qualifier() {
2253 let svc = LambdaService::new(make_state());
2254 let create_body = json!({
2255 "FunctionName": "MyFunc",
2256 "Runtime": "nodejs20.x",
2257 "Role": "arn:aws:iam::123456789012:role/lambda-role",
2258 "Handler": "index.handler",
2259 "Code": {"ZipFile": ""},
2260 })
2261 .to_string();
2262 let req = make_request(Method::POST, "/2015-03-31/functions", &create_body);
2263 svc.handle(req).await.expect("create function");
2264
2265 let req = make_request(Method::GET, "/2015-03-31/functions/MyFunc:1", "");
2266 let resp = svc
2267 .handle(req)
2268 .await
2269 .expect("get function by name:qualifier");
2270 assert_eq!(resp.status, StatusCode::OK);
2271 }
2272
2273 #[test]
2274 fn iam_condition_keys_for_add_permission_populates_arn_and_principal() {
2275 let svc = LambdaService::new(make_state());
2276 let body = json!({
2277 "StatementId": "stmt",
2278 "Action": "lambda:InvokeFunction",
2279 "Principal": "s3.amazonaws.com",
2280 })
2281 .to_string();
2282 let req = make_request(Method::POST, "/2015-03-31/functions/my-func/policy", &body);
2283 let action = fakecloud_core::auth::IamAction {
2284 service: "lambda",
2285 action: "AddPermission",
2286 resource: "arn:aws:lambda:us-east-1:123456789012:function:my-func".to_string(),
2287 };
2288 let keys = svc.iam_condition_keys_for(&req, &action);
2289 assert_eq!(
2290 keys.get("lambda:functionarn"),
2291 Some(&vec![
2292 "arn:aws:lambda:us-east-1:123456789012:function:my-func".to_string()
2293 ])
2294 );
2295 assert_eq!(
2296 keys.get("lambda:principal"),
2297 Some(&vec!["s3.amazonaws.com".to_string()])
2298 );
2299 }
2300
2301 #[test]
2302 fn iam_condition_keys_for_add_permission_omits_missing_principal() {
2303 let svc = LambdaService::new(make_state());
2304 let body = json!({"StatementId": "stmt", "Action": "lambda:InvokeFunction"}).to_string();
2305 let req = make_request(Method::POST, "/2015-03-31/functions/my-func/policy", &body);
2306 let action = fakecloud_core::auth::IamAction {
2307 service: "lambda",
2308 action: "AddPermission",
2309 resource: "arn:aws:lambda:us-east-1:123456789012:function:my-func".to_string(),
2310 };
2311 let keys = svc.iam_condition_keys_for(&req, &action);
2312 assert!(!keys.contains_key("lambda:principal"));
2313 assert!(keys.contains_key("lambda:functionarn"));
2314 }
2315
2316 #[test]
2317 fn iam_condition_keys_for_non_add_permission_is_empty() {
2318 let svc = LambdaService::new(make_state());
2319 let req = make_request(Method::GET, "/2015-03-31/functions/my-func", "");
2320 let action = fakecloud_core::auth::IamAction {
2321 service: "lambda",
2322 action: "GetFunction",
2323 resource: "arn:aws:lambda:us-east-1:123456789012:function:my-func".to_string(),
2324 };
2325 assert!(svc.iam_condition_keys_for(&req, &action).is_empty());
2326 }
2327
2328 #[tokio::test]
2329 async fn test_create_and_get_function() {
2330 let state = make_state();
2331 let svc = LambdaService::new(state);
2332
2333 let create_body = json!({
2334 "FunctionName": "my-func",
2335 "Runtime": "python3.12",
2336 "Role": "arn:aws:iam::123456789012:role/test-role",
2337 "Handler": "index.handler",
2338 "Code": { "ZipFile": "UEsFBgAAAAAAAAAAAAAAAAAAAAA=" }
2339 });
2340
2341 let req = make_request(
2342 Method::POST,
2343 "/2015-03-31/functions",
2344 &create_body.to_string(),
2345 );
2346 let resp = svc.handle(req).await.unwrap();
2347 assert_eq!(resp.status, StatusCode::CREATED);
2348
2349 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2350 assert_eq!(body["FunctionName"], "my-func");
2351 assert_eq!(body["Runtime"], "python3.12");
2352
2353 let req = make_request(Method::GET, "/2015-03-31/functions/my-func", "");
2355 let resp = svc.handle(req).await.unwrap();
2356 assert_eq!(resp.status, StatusCode::OK);
2357 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2358 assert_eq!(body["Configuration"]["FunctionName"], "my-func");
2359 }
2360
2361 #[tokio::test]
2362 async fn test_delete_function() {
2363 let state = make_state();
2364 let svc = LambdaService::new(state);
2365
2366 let create_body = json!({
2367 "FunctionName": "to-delete",
2368 "Runtime": "nodejs20.x",
2369 "Role": "arn:aws:iam::123456789012:role/test",
2370 "Handler": "index.handler",
2371 "Code": {}
2372 });
2373
2374 let req = make_request(
2375 Method::POST,
2376 "/2015-03-31/functions",
2377 &create_body.to_string(),
2378 );
2379 svc.handle(req).await.unwrap();
2380
2381 let req = make_request(Method::DELETE, "/2015-03-31/functions/to-delete", "");
2382 let resp = svc.handle(req).await.unwrap();
2383 assert_eq!(resp.status, StatusCode::NO_CONTENT);
2384
2385 let req = make_request(Method::GET, "/2015-03-31/functions/to-delete", "");
2387 let resp = svc.handle(req).await;
2388 assert!(resp.is_err());
2389 }
2390
2391 #[tokio::test]
2392 async fn test_invoke_without_runtime_returns_error() {
2393 let state = make_state();
2394 let svc = LambdaService::new(state);
2395
2396 let create_body = json!({
2397 "FunctionName": "invoke-me",
2398 "Runtime": "python3.12",
2399 "Role": "arn:aws:iam::123456789012:role/test",
2400 "Handler": "index.handler",
2401 "Code": {}
2402 });
2403
2404 let req = make_request(
2405 Method::POST,
2406 "/2015-03-31/functions",
2407 &create_body.to_string(),
2408 );
2409 svc.handle(req).await.unwrap();
2410
2411 let req = make_request(
2412 Method::POST,
2413 "/2015-03-31/functions/invoke-me/invocations",
2414 r#"{"key": "value"}"#,
2415 );
2416 let resp = svc.handle(req).await;
2417 assert!(resp.is_err());
2418 }
2419
2420 #[tokio::test]
2421 async fn test_invoke_nonexistent_function() {
2422 let state = make_state();
2423 let svc = LambdaService::new(state);
2424
2425 let req = make_request(
2426 Method::POST,
2427 "/2015-03-31/functions/does-not-exist/invocations",
2428 "{}",
2429 );
2430 let resp = svc.handle(req).await;
2431 assert!(resp.is_err());
2432 }
2433
2434 #[tokio::test]
2435 async fn test_list_functions() {
2436 let state = make_state();
2437 let svc = LambdaService::new(state);
2438
2439 for name in &["func-a", "func-b"] {
2440 let create_body = json!({
2441 "FunctionName": name,
2442 "Runtime": "python3.12",
2443 "Role": "arn:aws:iam::123456789012:role/test",
2444 "Handler": "index.handler",
2445 "Code": {}
2446 });
2447 let req = make_request(
2448 Method::POST,
2449 "/2015-03-31/functions",
2450 &create_body.to_string(),
2451 );
2452 svc.handle(req).await.unwrap();
2453 }
2454
2455 let req = make_request(Method::GET, "/2015-03-31/functions", "");
2456 let resp = svc.handle(req).await.unwrap();
2457 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2458 assert_eq!(body["Functions"].as_array().unwrap().len(), 2);
2459 }
2460
2461 #[tokio::test]
2462 async fn test_event_source_mapping() {
2463 let state = make_state();
2464 let svc = LambdaService::new(state);
2465
2466 let create_body = json!({
2468 "FunctionName": "esm-func",
2469 "Runtime": "python3.12",
2470 "Role": "arn:aws:iam::123456789012:role/test",
2471 "Handler": "index.handler",
2472 "Code": {}
2473 });
2474 let req = make_request(
2475 Method::POST,
2476 "/2015-03-31/functions",
2477 &create_body.to_string(),
2478 );
2479 svc.handle(req).await.unwrap();
2480
2481 let mapping_body = json!({
2483 "FunctionName": "esm-func",
2484 "EventSourceArn": "arn:aws:sqs:us-east-1:123456789012:my-queue",
2485 "BatchSize": 5
2486 });
2487 let req = make_request(
2488 Method::POST,
2489 "/2015-03-31/event-source-mappings",
2490 &mapping_body.to_string(),
2491 );
2492 let resp = svc.handle(req).await.unwrap();
2493 assert_eq!(resp.status, StatusCode::ACCEPTED);
2494 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2495 let uuid = body["UUID"].as_str().unwrap().to_string();
2496
2497 let req = make_request(Method::GET, "/2015-03-31/event-source-mappings", "");
2499 let resp = svc.handle(req).await.unwrap();
2500 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2501 assert_eq!(body["EventSourceMappings"].as_array().unwrap().len(), 1);
2502
2503 let req = make_request(
2505 Method::DELETE,
2506 &format!("/2015-03-31/event-source-mappings/{uuid}"),
2507 "",
2508 );
2509 let resp = svc.handle(req).await.unwrap();
2510 assert_eq!(resp.status, StatusCode::ACCEPTED);
2511 }
2512
2513 async fn seed_function(svc: &LambdaService, name: &str) {
2514 let body = json!({
2515 "FunctionName": name,
2516 "Runtime": "python3.12",
2517 "Role": "arn:aws:iam::123456789012:role/r",
2518 "Handler": "index.handler",
2519 "Code": {}
2520 });
2521 let req = make_request(Method::POST, "/2015-03-31/functions", &body.to_string());
2522 svc.handle(req).await.unwrap();
2523 }
2524
2525 #[tokio::test]
2526 async fn add_permission_builds_canonical_statement() {
2527 let svc = LambdaService::new(make_state());
2528 seed_function(&svc, "f").await;
2529
2530 let body = json!({
2531 "StatementId": "s3-invoke",
2532 "Action": "InvokeFunction",
2533 "Principal": "s3.amazonaws.com",
2534 "SourceArn": "arn:aws:s3:::my-bucket",
2535 "SourceAccount": "123456789012",
2536 });
2537 let req = make_request(
2538 Method::POST,
2539 "/2015-03-31/functions/f/policy",
2540 &body.to_string(),
2541 );
2542 let resp = svc.handle(req).await.unwrap();
2543 assert_eq!(resp.status, StatusCode::CREATED);
2544
2545 let out: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2546 let statement: Value = serde_json::from_str(out["Statement"].as_str().unwrap()).unwrap();
2547 assert_eq!(statement["Sid"], "s3-invoke");
2548 assert_eq!(statement["Effect"], "Allow");
2549 assert_eq!(statement["Principal"]["Service"], "s3.amazonaws.com");
2550 assert_eq!(statement["Action"], "lambda:InvokeFunction");
2551 assert_eq!(
2552 statement["Resource"],
2553 "arn:aws:lambda:us-east-1:123456789012:function:f"
2554 );
2555 assert_eq!(
2556 statement["Condition"]["ArnLike"]["aws:SourceArn"],
2557 "arn:aws:s3:::my-bucket"
2558 );
2559 assert_eq!(
2560 statement["Condition"]["StringEquals"]["aws:SourceAccount"],
2561 "123456789012"
2562 );
2563 }
2564
2565 #[tokio::test]
2566 async fn add_permission_aws_principal_emits_aws_key() {
2567 let svc = LambdaService::new(make_state());
2568 seed_function(&svc, "f").await;
2569
2570 let body = json!({
2571 "StatementId": "user-invoke",
2572 "Action": "InvokeFunction",
2573 "Principal": "arn:aws:iam::123456789012:user/alice",
2574 });
2575 let req = make_request(
2576 Method::POST,
2577 "/2015-03-31/functions/f/policy",
2578 &body.to_string(),
2579 );
2580 svc.handle(req).await.unwrap();
2581
2582 let req = make_request(Method::GET, "/2015-03-31/functions/f/policy", "");
2584 let resp = svc.handle(req).await.unwrap();
2585 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2586 let doc: Value = serde_json::from_str(body["Policy"].as_str().unwrap()).unwrap();
2587 let statements = doc["Statement"].as_array().unwrap();
2588 assert_eq!(statements.len(), 1);
2589 assert_eq!(
2590 statements[0]["Principal"]["AWS"],
2591 "arn:aws:iam::123456789012:user/alice"
2592 );
2593 assert!(statements[0].get("Condition").is_none());
2594 }
2595
2596 #[tokio::test]
2597 async fn add_permission_rejects_duplicate_statement_id() {
2598 let svc = LambdaService::new(make_state());
2599 seed_function(&svc, "f").await;
2600
2601 let body = json!({
2602 "StatementId": "dup",
2603 "Action": "InvokeFunction",
2604 "Principal": "arn:aws:iam::123456789012:user/a",
2605 });
2606 let req = make_request(
2607 Method::POST,
2608 "/2015-03-31/functions/f/policy",
2609 &body.to_string(),
2610 );
2611 svc.handle(req).await.unwrap();
2612
2613 let req = make_request(
2614 Method::POST,
2615 "/2015-03-31/functions/f/policy",
2616 &body.to_string(),
2617 );
2618 let err = match svc.handle(req).await {
2619 Err(e) => e,
2620 Ok(_) => panic!("expected error"),
2621 };
2622 assert_eq!(err.status(), StatusCode::CONFLICT);
2623 }
2624
2625 #[tokio::test]
2626 async fn get_policy_returns_404_when_no_policy_attached() {
2627 let svc = LambdaService::new(make_state());
2628 seed_function(&svc, "f").await;
2629
2630 let req = make_request(Method::GET, "/2015-03-31/functions/f/policy", "");
2631 let err = match svc.handle(req).await {
2632 Err(e) => e,
2633 Ok(_) => panic!("expected error"),
2634 };
2635 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2636 }
2637
2638 #[tokio::test]
2639 async fn remove_permission_strips_matching_sid_and_leaves_empty_doc() {
2640 let svc = LambdaService::new(make_state());
2641 seed_function(&svc, "f").await;
2642
2643 for sid in ["a", "b"] {
2644 let body = json!({
2645 "StatementId": sid,
2646 "Action": "InvokeFunction",
2647 "Principal": "arn:aws:iam::123456789012:user/u",
2648 });
2649 let req = make_request(
2650 Method::POST,
2651 "/2015-03-31/functions/f/policy",
2652 &body.to_string(),
2653 );
2654 svc.handle(req).await.unwrap();
2655 }
2656
2657 let req = make_request(Method::DELETE, "/2015-03-31/functions/f/policy/a", "");
2659 let resp = svc.handle(req).await.unwrap();
2660 assert_eq!(resp.status, StatusCode::NO_CONTENT);
2661
2662 let req = make_request(Method::GET, "/2015-03-31/functions/f/policy", "");
2664 let resp = svc.handle(req).await.unwrap();
2665 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2666 let doc: Value = serde_json::from_str(body["Policy"].as_str().unwrap()).unwrap();
2667 let stmts = doc["Statement"].as_array().unwrap();
2668 assert_eq!(stmts.len(), 1);
2669 assert_eq!(stmts[0]["Sid"], "b");
2670
2671 let req = make_request(Method::DELETE, "/2015-03-31/functions/f/policy/b", "");
2673 svc.handle(req).await.unwrap();
2674
2675 let req = make_request(Method::GET, "/2015-03-31/functions/f/policy", "");
2676 let resp = svc.handle(req).await.unwrap();
2677 let body: Value = serde_json::from_slice(resp.body.expect_bytes()).unwrap();
2678 let doc: Value = serde_json::from_str(body["Policy"].as_str().unwrap()).unwrap();
2679 assert_eq!(doc["Statement"].as_array().unwrap().len(), 0);
2680 }
2681
2682 #[tokio::test]
2683 async fn remove_permission_unknown_sid_is_404() {
2684 let svc = LambdaService::new(make_state());
2685 seed_function(&svc, "f").await;
2686
2687 let body = json!({
2688 "StatementId": "known",
2689 "Action": "InvokeFunction",
2690 "Principal": "arn:aws:iam::123456789012:user/u",
2691 });
2692 let req = make_request(
2693 Method::POST,
2694 "/2015-03-31/functions/f/policy",
2695 &body.to_string(),
2696 );
2697 svc.handle(req).await.unwrap();
2698
2699 let req = make_request(Method::DELETE, "/2015-03-31/functions/f/policy/other", "");
2700 let err = match svc.handle(req).await {
2701 Err(e) => e,
2702 Ok(_) => panic!("expected error"),
2703 };
2704 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2705 }
2706
2707 #[tokio::test]
2708 async fn add_permission_on_missing_function_is_404() {
2709 let svc = LambdaService::new(make_state());
2710 let body = json!({
2711 "StatementId": "s",
2712 "Action": "InvokeFunction",
2713 "Principal": "arn:aws:iam::123456789012:user/u",
2714 });
2715 let req = make_request(
2716 Method::POST,
2717 "/2015-03-31/functions/missing/policy",
2718 &body.to_string(),
2719 );
2720 let err = match svc.handle(req).await {
2721 Err(e) => e,
2722 Ok(_) => panic!("expected error"),
2723 };
2724 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2725 }
2726
2727 #[test]
2728 fn iam_action_for_maps_invoke_to_function_arn() {
2729 let svc = LambdaService::new(make_state());
2730 let req = make_request(Method::POST, "/2015-03-31/functions/f/invocations", "");
2731 let action = svc.iam_action_for(&req).unwrap();
2732 assert_eq!(action.service, "lambda");
2733 assert_eq!(action.action, "InvokeFunction");
2734 assert_eq!(
2735 action.resource,
2736 "arn:aws:lambda:us-east-1:123456789012:function:f"
2737 );
2738 }
2739
2740 #[test]
2741 fn iam_action_for_maps_list_to_star() {
2742 let svc = LambdaService::new(make_state());
2743 let req = make_request(Method::GET, "/2015-03-31/functions", "");
2744 let action = svc.iam_action_for(&req).unwrap();
2745 assert_eq!(action.action, "ListFunctions");
2746 assert_eq!(action.resource, "*");
2747 }
2748
2749 #[test]
2750 fn iam_action_for_create_reads_function_name_from_body() {
2751 let svc = LambdaService::new(make_state());
2752 let body = json!({
2753 "FunctionName": "newfn",
2754 "Runtime": "python3.12",
2755 "Role": "arn:aws:iam::123456789012:role/r",
2756 "Handler": "index.handler",
2757 "Code": {}
2758 });
2759 let req = make_request(Method::POST, "/2015-03-31/functions", &body.to_string());
2760 let action = svc.iam_action_for(&req).unwrap();
2761 assert_eq!(action.action, "CreateFunction");
2762 assert_eq!(
2763 action.resource,
2764 "arn:aws:lambda:us-east-1:123456789012:function:newfn"
2765 );
2766 }
2767
2768 #[tokio::test]
2771 async fn create_function_duplicate_returns_conflict() {
2772 let svc = LambdaService::new(make_state());
2773 seed_function(&svc, "dup-fn").await;
2774
2775 let body = json!({
2776 "FunctionName": "dup-fn",
2777 "Runtime": "python3.12",
2778 "Role": "arn:aws:iam::123456789012:role/r",
2779 "Handler": "index.handler",
2780 "Code": {"ZipFile": "UEsDBBQ="},
2781 });
2782 let req = make_request(Method::POST, "/2015-03-31/functions", &body.to_string());
2783 let err = match svc.handle(req).await {
2784 Err(e) => e,
2785 Ok(_) => panic!("expected ResourceConflictException"),
2786 };
2787 assert_eq!(err.status(), StatusCode::CONFLICT);
2788 }
2789
2790 #[tokio::test]
2791 async fn get_function_not_found() {
2792 let svc = LambdaService::new(make_state());
2793 let req = make_request(Method::GET, "/2015-03-31/functions/nope", "");
2794 let err = match svc.handle(req).await {
2795 Err(e) => e,
2796 Ok(_) => panic!("expected error"),
2797 };
2798 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2799 }
2800
2801 #[tokio::test]
2802 async fn delete_function_not_found() {
2803 let svc = LambdaService::new(make_state());
2804 let req = make_request(Method::DELETE, "/2015-03-31/functions/nope", "");
2805 let err = match svc.handle(req).await {
2806 Err(e) => e,
2807 Ok(_) => panic!("expected error"),
2808 };
2809 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2810 }
2811
2812 #[tokio::test]
2813 async fn get_event_source_mapping_not_found() {
2814 let svc = LambdaService::new(make_state());
2815 let req = make_request(
2816 Method::GET,
2817 "/2015-03-31/event-source-mappings/nonexistent",
2818 "",
2819 );
2820 let err = match svc.handle(req).await {
2821 Err(e) => e,
2822 Ok(_) => panic!("expected error"),
2823 };
2824 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2825 }
2826
2827 #[tokio::test]
2828 async fn delete_event_source_mapping_not_found() {
2829 let svc = LambdaService::new(make_state());
2830 let req = make_request(
2831 Method::DELETE,
2832 "/2015-03-31/event-source-mappings/nonexistent",
2833 "",
2834 );
2835 let err = match svc.handle(req).await {
2836 Err(e) => e,
2837 Ok(_) => panic!("expected error"),
2838 };
2839 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2840 }
2841
2842 #[tokio::test]
2843 async fn get_policy_on_missing_function() {
2844 let svc = LambdaService::new(make_state());
2845 let req = make_request(Method::GET, "/2015-03-31/functions/nope/policy", "");
2846 let err = match svc.handle(req).await {
2847 Err(e) => e,
2848 Ok(_) => panic!("expected error"),
2849 };
2850 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2851 }
2852
2853 #[tokio::test]
2854 async fn remove_permission_on_missing_function() {
2855 let svc = LambdaService::new(make_state());
2856 let req = make_request(
2857 Method::DELETE,
2858 "/2015-03-31/functions/nope/policy/stmt1",
2859 "",
2860 );
2861 let err = match svc.handle(req).await {
2862 Err(e) => e,
2863 Ok(_) => panic!("expected error"),
2864 };
2865 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2866 }
2867
2868 #[tokio::test]
2869 async fn publish_version_on_missing_function() {
2870 let svc = LambdaService::new(make_state());
2871 let req = make_request(Method::POST, "/2015-03-31/functions/nope/versions", "{}");
2872 let err = match svc.handle(req).await {
2873 Err(e) => e,
2874 Ok(_) => panic!("expected error"),
2875 };
2876 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2877 }
2878
2879 #[tokio::test]
2880 async fn unknown_route_returns_error() {
2881 let svc = LambdaService::new(make_state());
2882 let req = make_request(Method::POST, "/unknown/route", "{}");
2883 assert!(svc.handle(req).await.is_err());
2884 }
2885
2886 #[tokio::test]
2887 async fn publish_version_unknown_function_errors() {
2888 let svc = LambdaService::new(make_state());
2889 assert!(svc.publish_version("ghost", "123456789012").is_err());
2890 }
2891
2892 #[tokio::test]
2893 async fn get_function_unknown_errors() {
2894 let svc = LambdaService::new(make_state());
2895 assert!(svc
2896 .get_function("ghost", "123456789012", "us-east-1")
2897 .is_err());
2898 }
2899
2900 #[tokio::test]
2901 async fn delete_function_unknown_errors() {
2902 let svc = LambdaService::new(make_state());
2903 assert!(svc.delete_function("ghost", "123456789012").is_err());
2904 }
2905
2906 #[tokio::test]
2907 async fn get_event_source_mapping_unknown_errors() {
2908 let svc = LambdaService::new(make_state());
2909 assert!(svc
2910 .get_event_source_mapping("ghost", "123456789012")
2911 .is_err());
2912 }
2913
2914 #[tokio::test]
2915 async fn delete_event_source_mapping_unknown_errors() {
2916 let svc = LambdaService::new(make_state());
2917 assert!(svc
2918 .delete_event_source_mapping("ghost", "123456789012")
2919 .is_err());
2920 }
2921
2922 #[tokio::test]
2923 async fn list_functions_empty_ok() {
2924 let svc = LambdaService::new(make_state());
2925 let resp = svc.list_functions("123456789012").unwrap();
2926 assert_eq!(resp.status, http::StatusCode::OK);
2927 }
2928
2929 #[tokio::test]
2930 async fn list_event_source_mappings_empty_ok() {
2931 let svc = LambdaService::new(make_state());
2932 let resp = svc.list_event_source_mappings("123456789012").unwrap();
2933 assert_eq!(resp.status, http::StatusCode::OK);
2934 }
2935}