Skip to main content

fakecloud_lambda/
state.rs

1use chrono::{DateTime, Utc};
2use parking_lot::RwLock;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LambdaFunction {
9    pub function_name: String,
10    pub function_arn: String,
11    pub runtime: String,
12    pub role: String,
13    pub handler: String,
14    pub description: String,
15    pub timeout: i64,
16    pub memory_size: i64,
17    pub code_sha256: String,
18    pub code_size: i64,
19    pub version: String,
20    pub last_modified: DateTime<Utc>,
21    pub tags: HashMap<String, String>,
22    pub environment: HashMap<String, String>,
23    pub architectures: Vec<String>,
24    pub package_type: String,
25    pub code_zip: Option<Vec<u8>>,
26    /// Resource-based policy attached to this function via
27    /// `AddPermission`, serialized as a full JSON policy document
28    /// (`{"Version":"2012-10-17","Statement":[...]}`). `None` means
29    /// the function has no resource policy attached, matching the
30    /// `ResourceNotFoundException` AWS returns from `GetPolicy` in
31    /// that state. `AddPermission` lazily initializes this; every
32    /// `RemovePermission` leaves at least `{"Statement":[]}` behind,
33    /// matching AWS behavior.
34    pub policy: Option<String>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct EventSourceMapping {
39    pub uuid: String,
40    pub function_arn: String,
41    pub event_source_arn: String,
42    pub batch_size: i64,
43    pub enabled: bool,
44    pub state: String,
45    pub last_modified: DateTime<Utc>,
46}
47
48/// A recorded Lambda invocation from cross-service delivery.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct LambdaInvocation {
51    pub function_arn: String,
52    pub payload: String,
53    pub timestamp: DateTime<Utc>,
54    pub source: String,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct LambdaState {
59    pub account_id: String,
60    pub region: String,
61    #[serde(default)]
62    pub functions: HashMap<String, LambdaFunction>,
63    #[serde(default)]
64    pub event_source_mappings: HashMap<String, EventSourceMapping>,
65    /// Recorded invocations from cross-service integrations — not persisted.
66    #[serde(default, skip)]
67    pub invocations: Vec<LambdaInvocation>,
68}
69
70impl LambdaState {
71    pub fn new(account_id: &str, region: &str) -> Self {
72        Self {
73            account_id: account_id.to_string(),
74            region: region.to_string(),
75            functions: HashMap::new(),
76            event_source_mappings: HashMap::new(),
77            invocations: Vec::new(),
78        }
79    }
80
81    pub fn reset(&mut self) {
82        self.functions.clear();
83        self.event_source_mappings.clear();
84        self.invocations.clear();
85    }
86}
87
88pub type SharedLambdaState =
89    Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<LambdaState>>>;
90
91impl fakecloud_core::multi_account::AccountState for LambdaState {
92    fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
93        Self::new(account_id, region)
94    }
95}
96
97pub const LAMBDA_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
98
99#[derive(Debug, Serialize, Deserialize)]
100pub struct LambdaSnapshot {
101    pub schema_version: u32,
102    #[serde(default)]
103    pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<LambdaState>>,
104    #[serde(default)]
105    pub state: Option<LambdaState>,
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn new_has_empty_collections() {
114        let state = LambdaState::new("123456789012", "us-east-1");
115        assert_eq!(state.account_id, "123456789012");
116        assert_eq!(state.region, "us-east-1");
117        assert!(state.functions.is_empty());
118        assert!(state.event_source_mappings.is_empty());
119        assert!(state.invocations.is_empty());
120    }
121
122    #[test]
123    fn reset_clears_collections() {
124        let mut state = LambdaState::new("123456789012", "us-east-1");
125        state.invocations.push(LambdaInvocation {
126            function_arn: "arn".to_string(),
127            payload: "p".to_string(),
128            timestamp: Utc::now(),
129            source: "s".to_string(),
130        });
131        state.reset();
132        assert!(state.invocations.is_empty());
133    }
134}