1pub mod authz;
2pub mod error;
3mod executor;
4mod operations;
5pub mod state;
6mod util;
7
8pub use authz::LambdaResourcePolicyLookup;
9
10use std::path::Path;
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use awsim_core::{
15 AccountRegionStore, AwsError, BlobInventory, Body, BodyStore, Protocol, RequestContext,
16 RouteDefinition, ServiceHandler,
17};
18use serde_json::Value;
19use tracing::debug;
20
21use state::{LambdaState, LambdaStateSnapshot};
22
23pub struct LambdaService {
24 store: AccountRegionStore<LambdaState>,
25 body_store: Option<Arc<BodyStore>>,
26}
27
28impl LambdaService {
29 pub fn new() -> Self {
30 Self {
31 store: AccountRegionStore::new(),
32 body_store: None,
33 }
34 }
35
36 pub fn with_data_dir(dir: impl AsRef<Path>) -> Self {
37 Self {
38 store: AccountRegionStore::new(),
39 body_store: Some(Arc::new(BodyStore::new(dir.as_ref().to_path_buf()))),
40 }
41 }
42
43 pub fn with_max_blob_bytes(mut self, bytes: u64) -> Self {
44 if let Some(bs) = self.body_store.take() {
45 let root = bs.root().to_path_buf();
46 self.body_store = Some(Arc::new(BodyStore::new(root).with_max_size(bytes)));
47 }
48 self
49 }
50
51 fn get_state(&self, ctx: &RequestContext) -> Arc<LambdaState> {
52 let state = self.store.get(&ctx.account_id, &ctx.region);
53 if let Some(bs) = &self.body_store {
54 state.set_body_store(Arc::clone(bs));
55 }
56 state
57 }
58
59 pub fn store(&self) -> AccountRegionStore<LambdaState> {
60 self.store.clone()
61 }
62
63 pub fn body_store(&self) -> Option<&Arc<BodyStore>> {
64 self.body_store.as_ref()
65 }
66
67 pub const GROUPS: &'static [&'static str] = &["lambda"];
68
69 fn rebind_bodies(&self) {
70 let Some(bs) = &self.body_store else {
71 return;
72 };
73 for (_, state) in self.store.iter_all() {
74 state.set_body_store(Arc::clone(bs));
75 for mut entry in state.functions.iter_mut() {
76 let name = entry.key().clone();
77 let func = entry.value_mut();
78 if let Ok(path) = bs.blob_path("lambda", &name, "$LATEST") {
79 func.code = Some(Body::OnDisk(path));
80 }
81 for v in func.versions.iter_mut() {
82 if let Ok(path) = bs.blob_path("lambda", &name, &v.version) {
83 v.code = Some(Body::OnDisk(path));
84 }
85 }
86 }
87 }
88 }
89}
90
91impl Default for LambdaService {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97pub struct LambdaServiceInvoker {
106 store: AccountRegionStore<LambdaState>,
107}
108
109impl LambdaServiceInvoker {
110 pub fn new(store: AccountRegionStore<LambdaState>) -> Self {
111 Self { store }
112 }
113
114 fn normalise_function_name(reference: &str) -> &str {
115 if let Some(rest) = reference.strip_prefix("arn:")
116 && let Some(idx) = rest.rfind(':')
117 {
118 let tail = &rest[idx + 1..];
119 if let Some((name, _qualifier)) = tail.split_once(':') {
120 return name;
121 }
122 return tail;
123 }
124 reference
125 }
126}
127
128impl awsim_core::LambdaInvoker for LambdaServiceInvoker {
129 fn invoke(
130 &self,
131 function_name: &str,
132 payload: &Value,
133 account: &str,
134 region: &str,
135 ) -> Result<Value, AwsError> {
136 let state = self.store.get(account, region);
137 let name = Self::normalise_function_name(function_name);
138 let ctx = RequestContext::new("lambda", region);
139 let input = serde_json::json!({
140 "FunctionName": name,
141 "InvocationType": "RequestResponse",
142 "Payload": payload,
143 });
144 let response = operations::invocations::invoke(&state, &input, &ctx)?;
145 if let Some(err) = response.get("FunctionError").and_then(|v| v.as_str()) {
150 let payload = response
151 .get("Payload")
152 .cloned()
153 .unwrap_or(Value::Null)
154 .to_string();
155 return Err(AwsError::bad_request(
156 "LambdaInvocationError",
157 format!("Lambda {name} returned {err}: {payload}"),
158 ));
159 }
160 Ok(response.get("Payload").cloned().unwrap_or(Value::Null))
161 }
162}
163
164impl BlobInventory for LambdaService {
165 fn known_blobs(&self) -> Vec<(String, String, String)> {
166 let mut out = Vec::new();
167 for (_, state) in self.store.iter_all() {
168 for func_entry in state.functions.iter() {
169 let name = func_entry.key().clone();
170 out.push(("lambda".to_string(), name.clone(), "$LATEST".to_string()));
171 for v in func_entry.value().versions.iter() {
172 out.push(("lambda".to_string(), name.clone(), v.version.clone()));
173 }
174 }
175 }
176 out
177 }
178}
179
180#[async_trait]
181impl ServiceHandler for LambdaService {
182 fn service_name(&self) -> &str {
183 "lambda"
184 }
185
186 fn signing_name(&self) -> &str {
187 "lambda"
188 }
189
190 fn protocol(&self) -> Protocol {
191 Protocol::RestJson1
192 }
193
194 fn routes(&self) -> Vec<RouteDefinition> {
195 vec![
196 RouteDefinition {
198 method: "POST",
199 path_pattern: "/2015-03-31/functions",
200 operation: "CreateFunction",
201 required_query_param: None,
202 },
203 RouteDefinition {
204 method: "GET",
205 path_pattern: "/2015-03-31/functions",
206 operation: "ListFunctions",
207 required_query_param: None,
208 },
209 RouteDefinition {
210 method: "GET",
211 path_pattern: "/2015-03-31/functions/{FunctionName}",
212 operation: "GetFunction",
213 required_query_param: None,
214 },
215 RouteDefinition {
216 method: "DELETE",
217 path_pattern: "/2015-03-31/functions/{FunctionName}",
218 operation: "DeleteFunction",
219 required_query_param: None,
220 },
221 RouteDefinition {
222 method: "GET",
223 path_pattern: "/2015-03-31/functions/{FunctionName}/configuration",
224 operation: "GetFunctionConfiguration",
225 required_query_param: None,
226 },
227 RouteDefinition {
228 method: "PUT",
229 path_pattern: "/2015-03-31/functions/{FunctionName}/code",
230 operation: "UpdateFunctionCode",
231 required_query_param: None,
232 },
233 RouteDefinition {
234 method: "PUT",
235 path_pattern: "/2015-03-31/functions/{FunctionName}/configuration",
236 operation: "UpdateFunctionConfiguration",
237 required_query_param: None,
238 },
239 RouteDefinition {
240 method: "POST",
241 path_pattern: "/2015-03-31/functions/{FunctionName}/invocations",
242 operation: "Invoke",
243 required_query_param: None,
244 },
245 RouteDefinition {
247 method: "POST",
248 path_pattern: "/2015-03-31/functions/{FunctionName}/versions",
249 operation: "PublishVersion",
250 required_query_param: None,
251 },
252 RouteDefinition {
253 method: "GET",
254 path_pattern: "/2015-03-31/functions/{FunctionName}/versions",
255 operation: "ListVersionsByFunction",
256 required_query_param: None,
257 },
258 RouteDefinition {
260 method: "POST",
261 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases",
262 operation: "CreateAlias",
263 required_query_param: None,
264 },
265 RouteDefinition {
266 method: "GET",
267 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases",
268 operation: "ListAliases",
269 required_query_param: None,
270 },
271 RouteDefinition {
272 method: "GET",
273 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases/{Name}",
274 operation: "GetAlias",
275 required_query_param: None,
276 },
277 RouteDefinition {
278 method: "DELETE",
279 path_pattern: "/2015-03-31/functions/{FunctionName}/aliases/{Name}",
280 operation: "DeleteAlias",
281 required_query_param: None,
282 },
283 RouteDefinition {
285 method: "POST",
286 path_pattern: "/2015-03-31/event-source-mappings",
287 operation: "CreateEventSourceMapping",
288 required_query_param: None,
289 },
290 RouteDefinition {
291 method: "GET",
292 path_pattern: "/2015-03-31/event-source-mappings",
293 operation: "ListEventSourceMappings",
294 required_query_param: None,
295 },
296 RouteDefinition {
297 method: "GET",
298 path_pattern: "/2015-03-31/event-source-mappings/{UUID}",
299 operation: "GetEventSourceMapping",
300 required_query_param: None,
301 },
302 RouteDefinition {
303 method: "DELETE",
304 path_pattern: "/2015-03-31/event-source-mappings/{UUID}",
305 operation: "DeleteEventSourceMapping",
306 required_query_param: None,
307 },
308 RouteDefinition {
309 method: "PUT",
310 path_pattern: "/2015-03-31/event-source-mappings/{UUID}",
311 operation: "UpdateEventSourceMapping",
312 required_query_param: None,
313 },
314 RouteDefinition {
316 method: "POST",
317 path_pattern: "/2018-10-31/layers/{LayerName}/versions",
318 operation: "PublishLayerVersion",
319 required_query_param: None,
320 },
321 RouteDefinition {
322 method: "GET",
323 path_pattern: "/2018-10-31/layers",
324 operation: "ListLayers",
325 required_query_param: None,
326 },
327 RouteDefinition {
328 method: "GET",
329 path_pattern: "/2018-10-31/layers/{LayerName}/versions",
330 operation: "ListLayerVersions",
331 required_query_param: None,
332 },
333 RouteDefinition {
334 method: "DELETE",
335 path_pattern: "/2018-10-31/layers/{LayerName}/versions/{VersionNumber}",
336 operation: "DeleteLayerVersion",
337 required_query_param: None,
338 },
339 RouteDefinition {
341 method: "POST",
342 path_pattern: "/2021-10-31/functions/{FunctionName}/url",
343 operation: "CreateFunctionUrlConfig",
344 required_query_param: None,
345 },
346 RouteDefinition {
347 method: "GET",
348 path_pattern: "/2021-10-31/functions/{FunctionName}/url",
349 operation: "GetFunctionUrlConfig",
350 required_query_param: None,
351 },
352 RouteDefinition {
353 method: "DELETE",
354 path_pattern: "/2021-10-31/functions/{FunctionName}/url",
355 operation: "DeleteFunctionUrlConfig",
356 required_query_param: None,
357 },
358 RouteDefinition {
359 method: "GET",
360 path_pattern: "/2021-10-31/functions/{FunctionName}/urls",
361 operation: "ListFunctionUrlConfigs",
362 required_query_param: None,
363 },
364 RouteDefinition {
366 method: "PUT",
367 path_pattern: "/2017-10-31/functions/{FunctionName}/concurrency",
368 operation: "PutFunctionConcurrency",
369 required_query_param: None,
370 },
371 RouteDefinition {
372 method: "GET",
373 path_pattern: "/2019-09-30/functions/{FunctionName}/concurrency",
374 operation: "GetFunctionConcurrency",
375 required_query_param: None,
376 },
377 RouteDefinition {
378 method: "DELETE",
379 path_pattern: "/2017-10-31/functions/{FunctionName}/concurrency",
380 operation: "DeleteFunctionConcurrency",
381 required_query_param: None,
382 },
383 RouteDefinition {
385 method: "GET",
386 path_pattern: "/2024-08-31/functions/{FunctionName}/recursion-config",
387 operation: "GetFunctionRecursionConfig",
388 required_query_param: None,
389 },
390 RouteDefinition {
391 method: "PUT",
392 path_pattern: "/2024-08-31/functions/{FunctionName}/recursion-config",
393 operation: "PutFunctionRecursionConfig",
394 required_query_param: None,
395 },
396 RouteDefinition {
399 method: "PUT",
400 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
401 operation: "PutProvisionedConcurrencyConfig",
402 required_query_param: Some("Qualifier"),
403 },
404 RouteDefinition {
405 method: "GET",
406 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
407 operation: "GetProvisionedConcurrencyConfig",
408 required_query_param: Some("Qualifier"),
409 },
410 RouteDefinition {
411 method: "DELETE",
412 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
413 operation: "DeleteProvisionedConcurrencyConfig",
414 required_query_param: Some("Qualifier"),
415 },
416 RouteDefinition {
417 method: "GET",
418 path_pattern: "/2019-09-30/functions/{FunctionName}/provisioned-concurrency",
419 operation: "ListProvisionedConcurrencyConfigs",
420 required_query_param: None,
421 },
422 RouteDefinition {
424 method: "GET",
425 path_pattern: "/2017-03-31/tags/{Resource}",
426 operation: "ListTags",
427 required_query_param: None,
428 },
429 RouteDefinition {
430 method: "POST",
431 path_pattern: "/2017-03-31/tags/{Resource}",
432 operation: "TagResource",
433 required_query_param: None,
434 },
435 RouteDefinition {
436 method: "DELETE",
437 path_pattern: "/2017-03-31/tags/{Resource}",
438 operation: "UntagResource",
439 required_query_param: None,
440 },
441 RouteDefinition {
443 method: "GET",
444 path_pattern: "/2015-03-31/functions/{FunctionName}/policy",
445 operation: "GetPolicy",
446 required_query_param: None,
447 },
448 RouteDefinition {
449 method: "POST",
450 path_pattern: "/2015-03-31/functions/{FunctionName}/policy",
451 operation: "AddPermission",
452 required_query_param: None,
453 },
454 RouteDefinition {
455 method: "DELETE",
456 path_pattern: "/2015-03-31/functions/{FunctionName}/policy/{StatementId}",
457 operation: "RemovePermission",
458 required_query_param: None,
459 },
460 RouteDefinition {
462 method: "GET",
463 path_pattern: "/2016-08-19/account-settings",
464 operation: "GetAccountSettings",
465 required_query_param: None,
466 },
467 RouteDefinition {
469 method: "PUT",
470 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
471 operation: "PutFunctionEventInvokeConfig",
472 required_query_param: None,
473 },
474 RouteDefinition {
475 method: "GET",
476 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
477 operation: "GetFunctionEventInvokeConfig",
478 required_query_param: None,
479 },
480 RouteDefinition {
481 method: "DELETE",
482 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
483 operation: "DeleteFunctionEventInvokeConfig",
484 required_query_param: None,
485 },
486 RouteDefinition {
487 method: "POST",
488 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config",
489 operation: "UpdateFunctionEventInvokeConfig",
490 required_query_param: None,
491 },
492 RouteDefinition {
493 method: "GET",
494 path_pattern: "/2019-09-25/functions/{FunctionName}/event-invoke-config/list",
495 operation: "ListFunctionEventInvokeConfigs",
496 required_query_param: None,
497 },
498 ]
499 }
500
501 async fn handle(
502 &self,
503 operation: &str,
504 input: Value,
505 ctx: &RequestContext,
506 ) -> Result<Value, AwsError> {
507 debug!(operation, "Lambda request");
508 let state = self.get_state(ctx);
509
510 match operation {
511 "CreateFunction" => operations::functions::create_function(&state, &input, ctx),
513 "GetFunction" => operations::functions::get_function(&state, &input, ctx),
514 "GetFunctionConfiguration" => {
515 operations::functions::get_function_configuration(&state, &input, ctx)
516 }
517 "DeleteFunction" => operations::functions::delete_function(&state, &input),
518 "ListFunctions" => operations::functions::list_functions(&state, &input, ctx),
519 "UpdateFunctionCode" => {
520 operations::functions::update_function_code(&state, &input, ctx)
521 }
522 "UpdateFunctionConfiguration" => {
523 operations::functions::update_function_configuration(&state, &input, ctx)
524 }
525
526 "Invoke" => operations::invocations::invoke(&state, &input, ctx),
528
529 "PublishVersion" => operations::versions::publish_version(&state, &input, ctx),
531 "ListVersionsByFunction" => {
532 operations::versions::list_versions_by_function(&state, &input, ctx)
533 }
534
535 "CreateAlias" => operations::aliases::create_alias(&state, &input, ctx),
537 "GetAlias" => operations::aliases::get_alias(&state, &input, ctx),
538 "UpdateAlias" => operations::aliases::update_alias(&state, &input, ctx),
539 "DeleteAlias" => operations::aliases::delete_alias(&state, &input, ctx),
540 "ListAliases" => operations::aliases::list_aliases(&state, &input, ctx),
541
542 "CreateEventSourceMapping" => {
544 operations::event_source_mappings::create_event_source_mapping(&state, &input, ctx)
545 }
546 "GetEventSourceMapping" => {
547 operations::event_source_mappings::get_event_source_mapping(&state, &input, ctx)
548 }
549 "DeleteEventSourceMapping" => {
550 operations::event_source_mappings::delete_event_source_mapping(&state, &input, ctx)
551 }
552 "ListEventSourceMappings" => {
553 operations::event_source_mappings::list_event_source_mappings(&state, &input, ctx)
554 }
555 "UpdateEventSourceMapping" => {
556 operations::event_source_mappings::update_event_source_mapping(&state, &input, ctx)
557 }
558
559 "PublishLayerVersion" => operations::layers::publish_layer_version(&state, &input, ctx),
561 "ListLayers" => operations::layers::list_layers(&state, &input, ctx),
562 "ListLayerVersions" => operations::layers::list_layer_versions(&state, &input, ctx),
563 "GetLayerVersion" => operations::layers::get_layer_version(&state, &input, ctx),
564 "DeleteLayerVersion" => operations::layers::delete_layer_version(&state, &input, ctx),
565
566 "CreateFunctionUrlConfig" => {
568 operations::url_configs::create_function_url_config(&state, &input, ctx)
569 }
570 "GetFunctionUrlConfig" => {
571 operations::url_configs::get_function_url_config(&state, &input, ctx)
572 }
573 "DeleteFunctionUrlConfig" => {
574 operations::url_configs::delete_function_url_config(&state, &input, ctx)
575 }
576 "ListFunctionUrlConfigs" => {
577 operations::url_configs::list_function_url_configs(&state, &input, ctx)
578 }
579
580 "TagResource" => operations::tags::tag_resource(&state, &input, ctx),
582 "UntagResource" => operations::tags::untag_resource(&state, &input, ctx),
583 "ListTags" => operations::tags::list_tags(&state, &input, ctx),
584
585 "GetPolicy" => operations::permissions::get_policy(&state, &input, ctx),
587 "AddPermission" => operations::permissions::add_permission(&state, &input, ctx),
588 "RemovePermission" => operations::permissions::remove_permission(&state, &input, ctx),
589
590 "GetAccountSettings" => {
592 operations::permissions::get_account_settings(&state, &input, ctx)
593 }
594
595 "PutFunctionEventInvokeConfig" => {
597 operations::event_invoke_configs::put_function_event_invoke_config(
598 &state, &input, ctx,
599 )
600 }
601 "GetFunctionEventInvokeConfig" => {
602 operations::event_invoke_configs::get_function_event_invoke_config(
603 &state, &input, ctx,
604 )
605 }
606 "UpdateFunctionEventInvokeConfig" => {
607 operations::event_invoke_configs::update_function_event_invoke_config(
608 &state, &input, ctx,
609 )
610 }
611 "DeleteFunctionEventInvokeConfig" => {
612 operations::event_invoke_configs::delete_function_event_invoke_config(
613 &state, &input, ctx,
614 )
615 }
616 "ListFunctionEventInvokeConfigs" => {
617 operations::event_invoke_configs::list_function_event_invoke_configs(
618 &state, &input, ctx,
619 )
620 }
621
622 "PutFunctionConcurrency" => {
624 operations::concurrency::put_function_concurrency(&state, &input, ctx)
625 }
626 "GetFunctionConcurrency" => {
627 operations::concurrency::get_function_concurrency(&state, &input, ctx)
628 }
629 "DeleteFunctionConcurrency" => {
630 operations::concurrency::delete_function_concurrency(&state, &input, ctx)
631 }
632 "GetFunctionRecursionConfig" => {
633 operations::functions::get_function_recursion_config(&state, &input, ctx)
634 }
635 "PutFunctionRecursionConfig" => {
636 operations::functions::put_function_recursion_config(&state, &input, ctx)
637 }
638 "PutProvisionedConcurrencyConfig" => {
639 operations::concurrency::put_provisioned_concurrency_config(&state, &input, ctx)
640 }
641 "GetProvisionedConcurrencyConfig" => {
642 operations::concurrency::get_provisioned_concurrency_config(&state, &input, ctx)
643 }
644 "DeleteProvisionedConcurrencyConfig" => {
645 operations::concurrency::delete_provisioned_concurrency_config(&state, &input, ctx)
646 }
647 "ListProvisionedConcurrencyConfigs" => {
648 operations::concurrency::list_provisioned_concurrency_configs(&state, &input, ctx)
649 }
650
651 _ => Err(AwsError::unknown_operation(operation)),
652 }
653 }
654
655 fn snapshot(&self) -> Option<Vec<u8>> {
656 self.store.snapshot_to_bytes()
657 }
658
659 fn restore(&self, data: &[u8]) -> Result<(), String> {
660 use awsim_core::Snapshottable;
661 use state::LambdaRegionSnapshot;
662
663 if let Ok(()) = self.store.restore_from_bytes(data) {
664 self.rebind_bodies();
665 return Ok(());
666 }
667
668 let legacy: LambdaStateSnapshot =
669 serde_json::from_slice(data).map_err(|e| e.to_string())?;
670 let mut by_region: std::collections::HashMap<(String, String), Vec<_>> =
671 std::collections::HashMap::new();
672 for fs in legacy.functions {
673 by_region
674 .entry((fs.account_id.clone(), fs.region.clone()))
675 .or_default()
676 .push(fs);
677 }
678 self.store.clear();
679 for ((account_id, region), functions) in by_region {
680 let snap = LambdaRegionSnapshot {
681 account_id: account_id.clone(),
682 region: region.clone(),
683 functions,
684 };
685 let (acct, reg, state) = LambdaState::from_snapshot(snap);
686 self.store.set(&acct, ®, state);
687 }
688 self.rebind_bodies();
689 Ok(())
690 }
691
692 fn iam_action(&self, operation: &str) -> Option<String> {
693 match operation {
694 "CreateFunction"
695 | "GetFunction"
696 | "GetFunctionConfiguration"
697 | "DeleteFunction"
698 | "ListFunctions"
699 | "UpdateFunctionCode"
700 | "UpdateFunctionConfiguration"
701 | "Invoke"
702 | "InvokeFunction"
703 | "InvokeAsync"
704 | "PublishVersion"
705 | "ListVersionsByFunction"
706 | "CreateAlias"
707 | "GetAlias"
708 | "DeleteAlias"
709 | "ListAliases"
710 | "UpdateAlias"
711 | "CreateEventSourceMapping"
712 | "GetEventSourceMapping"
713 | "DeleteEventSourceMapping"
714 | "ListEventSourceMappings"
715 | "UpdateEventSourceMapping"
716 | "PublishLayerVersion"
717 | "ListLayers"
718 | "ListLayerVersions"
719 | "DeleteLayerVersion"
720 | "GetLayerVersion"
721 | "CreateFunctionUrlConfig"
722 | "GetFunctionUrlConfig"
723 | "DeleteFunctionUrlConfig"
724 | "ListFunctionUrlConfigs"
725 | "UpdateFunctionUrlConfig"
726 | "TagResource"
727 | "UntagResource"
728 | "ListTags"
729 | "GetPolicy"
730 | "AddPermission"
731 | "RemovePermission"
732 | "GetAccountSettings"
733 | "PutFunctionEventInvokeConfig"
734 | "GetFunctionEventInvokeConfig"
735 | "UpdateFunctionEventInvokeConfig"
736 | "DeleteFunctionEventInvokeConfig"
737 | "ListFunctionEventInvokeConfigs"
738 | "PutFunctionConcurrency"
739 | "GetFunctionConcurrency"
740 | "DeleteFunctionConcurrency"
741 | "PutProvisionedConcurrencyConfig"
742 | "GetProvisionedConcurrencyConfig"
743 | "DeleteProvisionedConcurrencyConfig"
744 | "ListProvisionedConcurrencyConfigs" => Some(format!("lambda:{operation}")),
745 _ => None,
746 }
747 }
748
749 fn iam_resource(&self, operation: &str, input: &Value, ctx: &RequestContext) -> Option<String> {
750 let prefix = format!("arn:aws:lambda:{}:{}", ctx.region, ctx.account_id);
751 match operation {
752 "ListFunctions"
753 | "ListEventSourceMappings"
754 | "ListLayers"
755 | "GetAccountSettings"
756 | "CreateFunction"
757 | "CreateEventSourceMapping" => Some("*".to_string()),
758 "TagResource" | "UntagResource" | "ListTags" => input
759 .get("Resource")
760 .and_then(|v| v.as_str())
761 .map(|s| s.to_string()),
762 "GetEventSourceMapping" | "DeleteEventSourceMapping" | "UpdateEventSourceMapping" => {
763 input
764 .get("UUID")
765 .and_then(|v| v.as_str())
766 .map(|uuid| format!("{prefix}:event-source-mapping:{uuid}"))
767 }
768 "PublishLayerVersion" | "ListLayerVersions" => input
769 .get("LayerName")
770 .and_then(|v| v.as_str())
771 .map(|name| format!("{prefix}:layer:{name}")),
772 "GetLayerVersion" | "DeleteLayerVersion" => {
773 let name = input.get("LayerName").and_then(|v| v.as_str())?;
774 let version = input
775 .get("VersionNumber")
776 .and_then(|v| v.as_i64())
777 .unwrap_or(0);
778 Some(format!("{prefix}:layer:{name}:{version}"))
779 }
780 _ => {
781 let name = input.get("FunctionName").and_then(|v| v.as_str())?;
782 if name.starts_with("arn:") {
783 Some(name.to_string())
784 } else {
785 Some(format!("{prefix}:function:{name}"))
786 }
787 }
788 }
789 }
790}