Skip to main content

awsim_lambda/
lib.rs

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
97/// Cross-service invoker that lets other services (Secrets Manager
98/// rotation, Cognito triggers, etc.) call a Lambda function
99/// synchronously without depending on the full `LambdaService`
100/// handler. Implements [`awsim_core::LambdaInvoker`].
101///
102/// `function_name` accepts either the bare function name or an
103/// `arn:aws:lambda:region:account:function:name(:qualifier)?` ARN;
104/// the invoker normalises to the bare name before dispatching.
105pub 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        // The `invoke` operation surfaces a `FunctionError` field when
146        // the Lambda runtime reported an error. AWS clients treat that
147        // as a logical failure even though the HTTP status is 200, so
148        // forward it as an `AwsError` so the caller can react.
149        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            // Functions
197            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            // Versions
246            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            // Aliases
259            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            // Event Source Mappings
284            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            // Layers
315            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            // Function URL Configs
340            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            // Reserved concurrency
365            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            // Recursion (self-invoke) control.
384            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            // Provisioned concurrency — single config (Get/Put/Delete) keys
397            // off the Qualifier query param; List omits it.
398            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            // Tags
423            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            // Policy / Permissions
442            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            // Account Settings
461            RouteDefinition {
462                method: "GET",
463                path_pattern: "/2016-08-19/account-settings",
464                operation: "GetAccountSettings",
465                required_query_param: None,
466            },
467            // Event Invoke Configs
468            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            // Functions
512            "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            // Invocations
527            "Invoke" => operations::invocations::invoke(&state, &input, ctx),
528
529            // Versions
530            "PublishVersion" => operations::versions::publish_version(&state, &input, ctx),
531            "ListVersionsByFunction" => {
532                operations::versions::list_versions_by_function(&state, &input, ctx)
533            }
534
535            // Aliases
536            "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            // Event Source Mappings
543            "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            // Layers
560            "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            // Function URL Configs
567            "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            // Tags
581            "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            // Policy / Permissions
586            "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            // Account Settings
591            "GetAccountSettings" => {
592                operations::permissions::get_account_settings(&state, &input, ctx)
593            }
594
595            // Event Invoke Configs
596            "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            // Reserved + provisioned concurrency
623            "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, &reg, 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}