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